Crossing the Assets Divide
July 4, 2012
While working on Smeagol, a read-only Gollum server and static site generator, I ran into a small problem with a larger design question hiding inside it.
The site can show an older page by selecting a Git revision. That sounds straightforward: load the page from the requested commit and render it. But what happens to the pictures?
Suppose an old installation guide includes a screenshot of its settings window. The guide has since been rewritten, and the screenshot replaced at the same path. If I open the old guide and get the new screenshot, I haven’t really retrieved the old guide. I’ve assembled a document that never existed.
The picture belongs to the content’s history. The site’s navigation bar usually doesn’t.
Two things we call an asset
It is convenient to put images, stylesheets, scripts, fonts, and everything
else that isn’t a page into one assets/ directory. Convenient, that is,
until we need to decide which version of each file to serve.
The useful distinction is between an asset’s roles:
| Content assets | Presentation assets |
|---|---|
| A screenshot explaining a particular software release | The site’s navigation icons |
| A chart supporting the article’s argument | The site’s general typography and layout |
| A downloadable example accompanying a tutorial | A script that opens the current site’s menu |
Content assets help make the page mean what it means. Presentation assets help the website display and navigate that page. A file extension won’t tell us which role an asset has: an image can belong to either column, and so can a script.
For a page-history viewer, I want the old content presented through the current site’s interface. Readers should retain the improved navigation, accessibility, and layout without silently receiving a different diagram or download from the one the page describes.
Make the version part of the request
Separating the files is a useful start:
assets/
site.css
navigation.js
logo.svg
media/
installation/settings.png
performance/results.csv
I use assets/ for presentation and media/ for content. The names aren’t
special. What matters is that the renderer can apply a different revision
policy to each group.
For example, a viewer could expose routes like these:
/history/<commit>/pages/installation.html
/history/<commit>/media/installation/settings.png
/assets/site.css
These are example routes, not a built-in Git feature. The history handler would read both the page and its content assets from the selected commit; the stylesheet would come from the current site deployment.
The renderer must carry that choice through when it constructs asset URLs.
If an old page still points at an unversioned /media/installation/settings.png,
moving the file into media/ has solved nothing. That request can still
fetch today’s screenshot.
A Git tag can be a convenient way to select a release, but I would resolve
it to a full commit ID before building the history links. The page and its
media then share one selected revision, even if the tag is moved later.
Git’s revision syntax also lets a
server identify a particular file in a commit as <commit>:<path>.
That choice helps with caching too: content from different revisions has different URLs. The site’s presentation can use its own deployment or content-hash versioning independently.
The boundary has exceptions
Sometimes a page’s behavior is part of its content. A JavaScript experiment or an interactive chart may depend on particular code and data. Those files belong to the historical example, even though they happen to be scripts or stylesheets. Applying today’s replacements could change the result or break it entirely.
A screenshot linked from an article may have been stored outside Git. A commit ID can’t recover an external file that was never preserved. Historical content needs retained copies of its dependencies, not merely links with old dates attached. If a required file is unavailable, the viewer should say so rather than quietly substitute the current one.
And sometimes the purpose really is to show the website as it was. A design archive should preserve the old presentation as well. That is a different viewing mode from reading an old document through today’s interface. Neither policy is universally right; the mistake is letting a shared directory name choose the policy accidentally.
For Smeagol’s page-history view, the split gives me a clear default: select a revision for the document and the things it refers to, then present them through the current site. The directory names are the easy part. Keeping that boundary intact all the way to the browser is the actual feature.