The whole idea, on one page
Lots of little pages
What the idea is, what it gives you for free, what it charges in return — and what happens the day you decide to change the whole layout.
The idea is Jim Nielsen's, in Lots of Little HTML Pages, and it fits in a sentence: building an interaction inside the page, progressively enhanced, is nearly always more work than writing two HTML pages and linking them.
It is not a ban on JavaScript. It is the observation that a good part of what we write in script is a reimplementation of what the browser already does — and does better, because it has been doing it for thirty years.
What changes in practice
A filter stops being a data- attribute read by a script and becomes /etiqueta/texto/. A sort order stops being a select that reorders the DOM and becomes /ordem/nome/. The menu stops being a drawer with aria-expanded and becomes /menu/. Every state you would have kept in memory becomes a file on disk.
| The usual interaction | Here |
|---|---|
| A menu sliding over the page | /menu/ |
| A search field filtering as you type | /indice/ |
| Filter buttons by category | /etiqueta/texto/ |
| Filter buttons by rarity | /raridade/esquecido/ |
| A sort-order picker | /ordem/nome/ |
| Two filters added together | /etiqueta/formularios/raridade/esquecido/ |
| A search result stuck on screen | /busca/?q=keyboard |
| A modal with the item's record | /especime/details/ |
What pays for that multiplication is the static site generator. It is the piece that makes the idea viable: writing 414 pages by hand would be insane, having a program write them is a few dozen lines of work.
What the browser gives you for free
- An address for everything
- Every state can be shared, bookmarked, opened in a new tab and sent in a message. An applied filter becomes a link someone else can open and see exactly the same thing.
- History and the back button
- No
history.pushState, no listening forpopstate, none of that classic defect where going back fails to undo the filter. - Focus, keyboard and screen reader
- On changing document, the browser resets focus and announces the new page. Reproducing that by hand, inside a single page, is one of the hardest — and most forgotten — parts of any interface built in script.
- Impossible states cease to exist
- There is no in-memory state to fall out of sync. If something looks wrong, reloading fixes it, because the page is the state.
- Failure arrives in small pieces
- If the CSS never lands, the document is still readable. If one page breaks, the others stay standing. There is no bundle whose failure takes the site down.
- Fast from the second click onwards
- The stylesheet is the same on every page and is already cached. What travels after that is a few kilobytes of HTML — less, nearly always, than the JSON a single-page app would fetch for the same effect.
- Testable with curl
- Every state is a URL. Checking whether a filter works means asking for the page and looking at what came back; there is no need to simulate clicks in a pretend browser.
What you pay in return
- Every click is a network trip
- On a poor connection, the difference between filtering in memory and asking for another document shows. Small HTML and prefetching soften it, but the cost is there and does not go away.
- State does not survive navigation
- A half-filled form, audio playing, an open conversation, an unsaved draft: none of it crosses the document swap. This is the hard limit of the idea, not an implementation detail.
- Combinable filters explode
- One facet with eight values gives eight pages. Two crossed would give twenty-four — except eight of those combinations hold no specimen, and the generator writes only the sixteen that exist, because the database is what knows which. Three facets would already give five hundred and twelve, and pruning stops saving you. While you can precompute only what has results, the sums work out; when you cannot, it is a server.
- No per-person content
- A static page is the same for everybody. Personalisation demands a server, and that is a different conversation.
- Header and footer repeated in every file
- A few kilobytes multiplied by dozens of pages. Compression eats nearly all the repetition, but the storage and publishing cost is real.
- A build step now exists
- Editing a text is no longer just editing a file: it is editing and regenerating. In exchange, what goes live has no dependency running.
And if the layout is swapped
This is nearly everyone's first question when faced with 414 HTML files: what about the day I want to change the design of everything? The answer depends on where the change lives — and, most of the time, it lives in the cheap place.
- The CSS changed
- Colour, typography, spacing, grid, dark mode, behaviour on narrow screens: all of it lives in a single file of 35 kB, served to every page. No page is regenerated and nothing is republished but that file. This is the majority of layout changes.
- The whole layout was swapped
- This site has 3 — classic, disruptive and gamer — over the same structure. Each is a file of tokens: colour, typography, border width, corner radius, shadow, uppercase, and it fits in a few kilobytes. The shared structure also accepts being overridden, for the day a layout needs to touch the grid and not just the skin — only then the theme file costs three times as much. The selector at the top of the page is not a stateful switch: it is links to the same page at another address. And the cost shows up in the sums, honestly: this site's 414 pages are 138 × 3 themes — the combinatorial explosion described above, happening right here.
- The HTML structure changed
- A new class, an extra block in the header, one more column in the index: that needs the generator run again. It rewrites the 414 pages in milliseconds. The cost is linear and the machine pays it, not you.
- The cache changed
- An HTML change invalidates every page on the CDN; a CSS one invalidates a single file. Which is why it is worth versioning the stylesheet's name —
estilo.a3f19c.css— and publishing by difference, comparing checksums instead of sending everything again. - The scale changed
- 414 pages take milliseconds. Fifty thousand take minutes, and then incremental builds, artifact caches and on-demand generation come in. The turning point is usually when the build passes a minute: until then, regenerating everything is simpler than any clever strategy.
The real risk is not build time: it is a change breaking a page nobody opened. The defence is having few templates shared by many pages — eight, here — and a check that walks the whole site on every build. This project's own confirms that all the HTML closes properly and that the 8382 internal links lead somewhere. It takes under a second.
Where the ruler turns
- Navigation and menupage
- Filter by one facetpage
- Sort a listpage
- Open an item's recordpage
- Find an exact termpage + Ctrl+F
- Validate form fieldspage
- Cross two facetspage, only the ones that exist
- Ranked searchpage, but with a server
- Recalculate while dragging a controlpage + script
- Cross three or more facetsserver or script
- Map, canvas, four-handed editingscript
a page handles itneeds script
It is not a contest between pages and script. It is a ruler, and most of the interactions on a content site fall well to the left of it — further left than we tend to admit when starting a project.
What still needed saying
- Cross-document view transitions
- The rule
@view-transition { navigation: auto }removed the last excuse for keeping everything on one page: the animation between states now belongs to the browser. Where support is missing, navigation simply happens without animation, and nobody goes without the content. - Speculation rules
- A
<script type="speculationrules">with declarative JSON tells the browser to preload the likely pages, which erases much of the click latency. It is the onlyscripttag usually worth having here — and it runs no code of yours. - Progressive enhancement still applies
- The idea is not to abolish JavaScript, it is to change the order: first the page exists and works, then script makes something nicer. What breaks when the script does not arrive is the ornament, not the content.
- This is old, and that is fine
- It is how the web worked before 2010, with two differences that change the verdict: static generators became trivial to write, and the browser gained cross-document view transitions.
This site, in numbers
- HTML pages
- 414
- HTML in total
- 2486 kB
- Largest page
- 21 kB
- Stylesheet
- 35 kB
- Internal links
- 8382
- Themes
- 3
- JavaScript
- 0 bytes
That is 30 specimens catalogued across 414 pages. To see the same idea from the inside, about explains how this site is put together, and the index shows everything here in a single document.