Website Antipatterns
Common antipatterns that break consistency, maintainability, and user experience.
What Is an Antipattern?
An antipattern is a common approach that seems helpful but causes more problems than it solves. These documents catalog the antipatterns to avoid and the right way to do things.
1. Copy-Pasting Navigation and Footers
Antipattern: Every page contains a full copy of the navbar (40+ lines) and footer (85+ lines) as inline HTML. When you need to update the nav, you must edit 60+ files.
Why it fails:
- Changes require editing every page individually — error-prone and time-consuming
- Inconsistencies creep in: different pages link to different destinations, use different badge spacing, etc.
- A single typo in one page's footer goes unnoticed
- The site grows stale because updates are too tedious to apply everywhere
Right way: Extract shared components (navbar, footer, scripts) into fragment files and load them via JavaScript. Define the nav and footer in ONE place.
<!-- Page only contains placeholders -->
<div id="nav-placeholder"></div>
... page content ...
<div id="footer-placeholder"></div>
<script src="js/include.js"></script>
<!-- includes/nav.html defines the nav once -->
2. Hand-Calculating Relative Paths
Antipattern: Each page manually computes paths like `../../../css/styles.css` or `../../products/sam.html` based on its directory depth. When pages move or a new directory level is added, all paths break.
Why it fails:
- A page at depth 3 uses `../../../` but gets moved to depth 2 — now all paths 404
- Developers copy a page from one directory to another and forget to adjust paths
- Inconsistencies: some pages use `../docs/index.html`, others use `../../index.html` for the same destination
- External links like GitHub URLs are easy to get right; relative paths are error-prone
Right way: Calculate paths at runtime based on the page's URL depth. A single JS function handles all depth levels.
// js/include.js calculates the correct root prefix
function calculatePaths() {
var parts = window.location.pathname.split('/');
var dirParts = parts.slice(1, -1); // remove filename and leading empty
var root = '../'.repeat(dirParts.length);
return { root: root, docs: root + 'docs/' };
}3. Inline <style> Blocks in HTML
Antipattern: Pages define their typography, layout, and component styles inside <style> tags in the <head>. Each page has its own copy.
Why it fails:
- Styles diverge between pages — same element looks different on different pages
- When you want to change a heading size or color, you must edit 60+ files
- Bloat: the same CSS is shipped on every page, just duplicated
- Impossible to maintain a consistent design system
Right way: All shared styles live in css/styles.css. Pages only include the external stylesheet.
4. Redundant UI Elements
Antipattern: Adding elements that duplicate information already present. For example, a "product switcher" dropdown on the docs hub page when the page already has product cards below it. Or a "Better Together" section when the hero already explains the ecosystem.
Why it fails:
- Cognitive overload: users see the same information twice
- Maintenance burden: when one copy is updated, the other is forgotten
- Visual noise: the page feels cluttered and unfocused
Right way: One element per piece of information. If the docs hub has product cards, don't also add a product switcher dropdown.
5. Having Two Sources of Truth
Antipattern: Writing documentation that duplicates or contradicts the source code. For example, listing 10 tools when the source has 11, or showing a model name that's been deprecated.
Why it fails:
- Documentation drifts from reality — users follow docs that don't match the software
- Trust erodes: when docs are wrong, users stop believing them
- Maintaining two copies means updating both when changes happen
Right way: Read source code before writing documentation. Verify every fact against ../SAM/Sources/, ../CLIO/lib/CLIO/, or ../ALICE/src/. If a fact can't be verified, label it [NEEDS VERIFICATION].
6. Specific Model Names in Docs
Antipattern: Listing specific LLM model names like "GPT-4o" or "Claude 3.5 Sonnet" in documentation examples.
Why it fails:
- Model names change frequently — "GPT-4o" becomes "o1" or "GPT-5"
- Docs become stale within months, misleading users
- Different providers offer different models at different times
Right way: Use provider-level descriptions. Say "Set model (run /api models to list available)" instead of "Set model (gpt-4o, claude-3-5-sonnet, etc.)".
7. ES Module Scripts in Simple HTML
Antipattern: Using <script type="module"> with import statements for libraries in simple static HTML pages.
Why it fails:
- ES module imports require CORS headers that
python3 -m http.serverdoesn't set - Breaks in production if the CDN is unreachable
- Simpler script tags work everywhere without configuration
Right way: Use standard <script src="..."> tags for CDN libraries like Mermaid.js.
Prevention
To avoid these antipatterns in future sessions:
- Always read the .doc-template.html first — it defines the intended page structure
- Check if a fragment already exists in
includes/before copy-pasting markup - Run the link validator (
python3 scripts/validate-links.py) before committing - Verify against source code — read
../CLIO/lib/CLIO/,../SAM/Sources/,../ALICE/src/ - Test in the browser — don't just check HTTP 200, actually inspect the rendered page