Hand-coding navigation menus before frameworks existed
Web
Explore the evolution of navigation menus from manual HTML edits to SSIs, drawing parallels to modern deterministic automation vs. LLM agents in architecture.
Shipping a large static website with dozens of pages in 1999 presented a unique kind of terror. It wasn't just the HTML; it was the `sed` command you’d have to run when a stakeholder demanded a minor change in the main navigation. One typo in that regular expression, and you’d broken all relative links by one directory level, moments before a Friday afternoon deploy. The anxiety was palpable.
We didn't have content management systems or component-based frameworks abstracting away the boring parts. Every single HTML file was an island, and the navigation menu was the brittle, hand-built bridge connecting them all. Keeping those bridges identical was a job in itself.
The Brute-Force Era of Copy and Paste
The first solution was always the most obvious: brute force. You’d get the navigation looking perfect on `index.html`, select the entire block of HTML from `<nav>` to `</nav>`, and copy it. Then, you would methodically open `about.html`, `services.html`, `contact.html`, and every other file, paste the new block, and save.
This never worked perfectly. You’d inevitably miss a file. Or you'd paste it in the wrong place. Worse, you’d forget to update the one link that needed to be different on a specific page. The result was a maddening user experience where the navigation bar would subtly shift or flicker between pages. It looked unprofessional and, frankly, broken. It was a direct reflection of our process: manual, error-prone, and fragile.
The core problem was a violation of a principle we didn't have a common name for yet: Don't Repeat Yourself (DRY). This fundamental concept, later formalized by Andrew Hunt and David Thomas in their seminal book, The Pragmatic Programmer, states that every piece of knowledge must have a single, unambiguous, authoritative representation within a system. Every page repeated the same structural code, turning a single logical entity—the site's navigation—into dozens of disconnected copies.
Our First Taste of Automation: Server-Side Includes
The breakthrough for many of us was the humble Server-Side Include (SSI), supported by web servers like Apache via its `mod_include` module. It was a simple directive processed by the web server before the page was sent to the user. Instead of pasting the full navigation block into every file, you’d replace it with a single line:
<!--#include virtual="/includes/navigation.html" -->
While other server-side scripting approaches like Perl/CGI offered similar capabilities, SSI was often the simplest entry point for many webmasters, requiring no separate programming language. Suddenly, the entire problem vanished. The navigation now lived in one file: `navigation.html`. A change request that previously took an hour of high-stress, manual editing now took 30 seconds. This was a revelation. It was my first real lesson in the power of deterministic automation. There was no magic; just a simple, predictable instruction to the server to assemble a page from parts. It worked every single time, without fail.
This pattern—centralizing a repeated element and including it where needed—is the direct ancestor of every component, partial, and template system we use today, from `
Solving the "You Are Here" Problem
Centralizing the menu created a new, more interesting problem. How do you highlight the current page? If `navigation.html` is the same for every page, how does the "About Us" link get an `active` class when you're on `about.html`?
This is where we moved from simple inclusion to rudimentary logic. One common pattern in projects using early PHP, for instance, involved setting a variable on each page before the include:
On about.php:
<?php $currentPage = 'about'; ?>
<?php include 'navigation.php'; ?>
Inside navigation.php:
<a href="/about.php" class="<?php if ($currentPage === 'about') { echo 'active'; } ?>">About Us</a>
This was powerful. We were now passing state into our deterministic component. The navigation file contained the logic, and each page provided the specific context. The system was still simple, predictable, and had zero ambiguity. The "About Us" link would be active if, and only if, the `$currentPage` variable was set to 'about'. No surprises, no failure modes beyond a typo in the variable name. It was testable, reliable, and easy to reason about—the hallmarks of good architecture.
What This Teaches Us About Modern Systems
This journey from manual duplication to programmatic inclusion feels ancient, but the core principles are what we wrestle with today in the world of AI-powered systems. The hand-coded navigation menu is the epitome of Software 1.0: deterministic automation where its behavior is fully specified and completely predictable. It does exactly what it's told, every time, much like the traditional code described by Andrej Karpathy in his influential essay, 'Software 2.0'.
This stands in stark contrast to the emerging world of agentic work, where we give an LLM-based agent a goal and allow it to figure out the steps. An agent might build a novel, perfectly contextual navigation for a user on the fly, dynamically adapting to their journey. But it could also hallucinate a 'Careers' link for a one-person business or fail to include a crucial 'Contact' page because it misinterpreted the user's immediate goal. This trades perfect predictability for powerful, flexible capability, but introduces entirely new classes of failure modes.
The real craft of architecture today is knowing where to draw the line. Which parts of your system need the boring, 3am-proof reliability of an old-school server-side include? And which parts benefit from the dynamic, and sometimes unpredictable, power of an agent? The lesson from the humble navigation menu is that the patterns of reliability and maintainability are timeless. Abstraction and automation are powerful, but only when they serve a foundation of predictable, understandable logic.
Concrete Takeaways
- Centralize, Don't Duplicate: The cost of repeating yourself isn't just the extra code; it's the certainty of future inconsistency. This principle (DRY) is as critical now as it was then.
- Prefer Deterministic Systems for Core Functions: For critical infrastructure like navigation, security, or core business logic, predictable and boring is a feature, not a bug.
- Understand the Trade-offs: The shift from deterministic automation to agentic systems isn't an upgrade; it's a trade-off. You are exchanging perfect reliability for greater flexibility and capability. The architect's job is to make that trade consciously.