This is a little addendum to my previous post about static vs. dynamic sites. A couple of people have mentioned that while they like the idea of offloading their site to a CDN (such as AWS S3), they’re not ready to have their site look like some relic from 1999.
One of the most obvious changes since the bad old days is the
helpfulness of modern web forms. Rather than expect you to scroll
through a <select>
list of 10,000 items, they use widgets like
jQueryUI Autocomplete to help you
find your choice, and they use AJAX calls to load subsets of the list
of choices
dynamically,
rather than having to load the whole list upfront.
For example, a postcode selector could be implemented something like this:
$("input.postcode").autocomplete({
minLength: 2,
source: '/api/postcodes/'
});
… and on the backend, the /api/postcodes/
URL would connect to a
handler (django view, etc) which asks the database to
SELECT code, name FROM postcodes WHERE name LIKE ?
or similar. This
works great, except that everytime a user goes and selects a postcode,
that’s another dynamic query your backend has to deal with, despite the
postcode data hardly ever changing.
To get around this, we could export the whole postcodes database to a single file and load the whole thing once the page has rendered. It’d still look better than stuffing all the postcode data into an HTML select box, but we can do better.
The autocomplete widget will accept a function as the data source, so we can have more control over the URL which is used to retrieve the data. For example, we can change the URL format used:
$("input.postcode").autocomplete({
minLength: 2,
source: function(request, response) {
var match = request.term.toUpperCase().match(/[A-Z]{2}/);
if (match) {
$.ajax(
'/postcodes/search_' + match[0] + '.json'
).done(function (data) {
var termre = new RegExp($.ui.autocomplete.escapeRegex(request.term));
response(data.filter(
function (x) { return termre.test(x); }
));
}).fail(function () {
response([]);
});
} else {
response([]);
}
}
});
… this appears to be dynamic, in that the user gets a filtered list,
but it is actually being fed from a group of small JSON files in the
directory /postcodes/
. As these are static files, they can be uploaded
to a CDN, get cached by the browser, etc.
Of course, the downside is that you now need a way to maintain 676
little JSON files, search_AA.json
through search_ZZ.json
. The
easiest way to do this depends on your framework …
In Django, you write models and views to return the proper data. This is handy for development anyway, since you can generate the pages dynamically in development and statically in production
models.py
:
from django.db import models
class Postcode(models.Model):
code = models.CharField(max_length=4)
name = models.CharField(max_length=200)
views.py
:
from postcode.models import Postcode
from django.http import HttpResponse
import json
def postcode_search(request, term):
data = [
"%s %s" % (pc.name, pc.code)
for pc in Postcode.objects.filter(name__istartswith=term)
]
return HttpResponse(
json.dumps(data),
mimetype="application/json"
)
urls.py
:
from django.conf.urls import patterns, include, url
from postcode.views import *
urlpatterns = patterns('',
url(r'^postcode/search_(?P<term>\w+).json', postcode_search),
)
Django Medusa provides an
easy way to replicate your dynamic content to static content instead.
Medusa needs some hints as to which files to generate, which go in a
file called renderers.py
:
from django_medusa.renderers import StaticSiteRenderer
import string
class PostcodeSearchRenderer(StaticSiteRenderer):
def get_paths(self):
for a in string.uppercase:
for b in string.uppercase:
yield "/postcode/search_%s.json" % (a+b)
renderers = [
PostcodeSearchRenderer,
]
Now when you run manage.py staticsitegen
, all 676 itty bitty JSON
files will be created.
Based on my postcode database, the biggest of these files,
search_MO.json
is 13kB, which is a big improvement over the ~300kB
required to download the whole thing. There are 429 files which are just
[]
… there aren’t many suburb names starting with QZ
.
Obviously, this approach could be made more sophisticated, with variable length prefixes and so on. But with this relatively simple approach we’ve already achieved an appearance of dynamic content with an entirely static backend.