Yesterday I blogged some tips for working with HarpJS. These tips came about from working on my latest project, the conversion of dynamic site into a static one. In this blog post I thought I'd share the experience. I blogged about this before, but the last site was very simple and nearly 15 years old. This time I worked on something a bit more beefy - the ColdFusion Cookbook.

The ColdFusion Cookbook was built about five or so years ago. It used a ColdFusion MVC framework (Model-Glue) and MySQL for the back end. It wasn't terribly complex, consisting of the following features/views:

  • A home page that reported the last few entries added
  • Categories that listed related content
  • An entry view page with color coding and tag/feature linking (so if an entry mentioned ColdFusion tag X it would be detected and linked to in the official docs)
  • A basic FAQ
  • A submit form to suggest new content
  • A RSS feed
  • PDF generation of the entire content set for download
  • And an Admin tool for editing categories and content

The site gets around 4000 page views a month, so it wasn't high traffic, but certainly not terrible either. (It makes a bit of money as well - enough to pay the domain fees a few times over.) Content submissions though have dwindled to - well - nothing. I got an entry when I recently reopened it, but nothing since then. To me this was a perfect candidate for moving it to static. I'd still be able to add content (if someone cared to submit any) but I'd not have to worry about the application (or database) server crashing.

I began by creating the layout and a simple home page. This was relatively trivial. I basically viewed the HTML version of the page (not the ColdFusion source) and copied and pasted. I use EJS for my templating because Jade forces you to kill a kitten for every template you write. The difficult aspect was the box in the upper left hand corner.

It changes for every file and ends up being a bit complex at times. For the top level pages it is pretty direct, but for categories it is based on the current category being shown. Finally, for entries it is blank. So I ended up with the following logic.


<div id="desc">
<% if(current.path.length === 1) { %>	
<h2> <%= public._data[current.path].headertitle %></h2>
<p>
<%= public._data[current.path].headertext %>
</p>
<% } else if(current.path.length === 2 && current.path[0] == "category") { %>
	<h2><%- public.category._data[current.source].title %></h2>
	<p>
		<%- public.category._data[current.source].desc %>
	</p>
<% } %>
</div>

What you see there is logic that looks at both the Current scope (HarpJS passes information about where you are request-wise) as well as the Public scope for a particular category. You create metadata for your content via JSON files. The structure of that metadata is whatever you want - so in my case my categories had a title and a desc field. Here is a snippet.

{
	"Application-Management":{
		"title":"Application Management",
		"desc":"This covers anything related to applications, including session and client tracking."
	},
	"Caching":{
		"title":"Caching",
		"desc":"This category covers a full range of caching options and tricks that are available to ColdFusion."
	},

I built this JSON by hand as it was a short list, but I should have automated it. (I did automate a part of the process - more on that in a bit.)

The site had 151 entries stored in 2 MySQL tables. One stored the main entry information and another was a join table for entries to categories. I first created an entry template and figured out what made sense with my JSON data. I did this with static data obviously and ensured things worked OK. For example, my home page needed to count the data and list the most recent entries. I went back and forth between using an array versus a simple object and went with the object form as it worked better with HarpJS's built in URL matching system. This resulted in slightly awkward code in my home page template. Note the use of Object.keys below.

<p>
There are currently <b><%= Object.keys(public.entries._data).length %></b> entries released!
</p>

<h3>Recent Entries</h3>

<p>
<%
	for(var i=0; i<Math.min(10, Object.keys(public.entries._data).length); i++) {
		var key = Object.keys(public.entries._data)[i];
		var entry = public.entries._data[key];
%>
	<a href="/entries/<%= key %>.html"><%= entry.title %></a><br/>
<%
	}
%>
</p>

Once I felt like entries were OK with my fake data, I then switched back to ColdFusion. On my dev server I wrote a script that:

  • Selected all the entries.
  • Generated a JSON string that I'd copy and paste into my HarpJS app.
  • Created flat files for every entry. This proved to be a bit difficult as I had some rather intense formatting going on. I had decided to remove the 'auto link' for tags so that was simpler, but I really wanted clean HTML for my entries. Previously I had used some sloppy code that rendered well enough. I probably spent more time on this than any other aspect. But once done I was able to quickly generate all my static content and output crap to copy into the Harp copy.

One final thing I'll show you is the page that categories use to list content. I blogged about this already so it isn't new, but it was nice to try it out on a real project. This code exists in a partial that is loaded by every category page.

<h3>Questions</h3>

<% 
	function getCat(input) {
		for(var c in public.category._data) {
			if(c === input) return public.category._data[c].title;	
		}
	}

	function getEntries(cat,entries) {
		var results = [];
		var keys = Object.keys(entries);
		for(var i=0; i<keys.length; i++) {
			var cats = entries[keys[i]].categories.split(",");
			if(cats.indexOf(cat) >= 0) {
				results.push(entries[keys[i]]);	
				results[results.length-1].key = keys[i];
			}
		}
		return results;
	}
	
	var entries = getEntries(getCat(current.source), public.entries._data);
	for(var i=0; i<entries.length; i++) { 
		entry = entries[i];
%>
	<p>
		<a href='/entries/<%- entry.key %>.html'><%- entry.title %></a><br/>
	</p>
<% } %>

The final step was to run "harp compile" and convert the application into static HTML files. What's fascinating is the change in size. In the screen shot below, the right side is the new version and includes both the source HarpJS and the production out.

To be fair, the old dynamic version had other folders of stuff I wasn't using and a large MVC framework. I ended up removing the PDF version for now since - again - as far as I knew this wasn't something people used. Search used to be driven by Solr. I switched to Google. Something tells me Google knows search. One tip though. Note that my site has a search box in the UI. How do you integrate that with Google's Custom Search tool so that when the page loads, the embedded search will use the initial input?

Simple - first - change your form to use GET. Then note the URL parameter used for the field and add it to the Google embed code.

<gcse:search queryParameterName="searchterms"></gcse:search>

In the code sample above the modification is the addition of queryParameterName. I should also point out I got rid of the contact form. It is certainly possible to replace it (see my article: Moving to Static and Keeping Your Toys) but since no one had used it for a while I went with the simpler route (i.e., I tell you to email me).

The final final step was to create a S3 Amazon bucket, upload the files, and update DNS. That's it. I realized during the writing of this article that my dev server was missing the latest entry so I'll be adding that soon. I don't have the fancy web based admin anymore so this will take me about 4 minutes - well worth it in my mind for the peace of mind of having absolutely no back end server I have to worry about.

Want to see the code? I've included a zip of the project in this entry. It includes everything but the custom ColdFusion code to output the static crap.

Download attached file.