~linuxgoose/quillhut-static

8c9b43ea5f9f10f71b108864229ee026a9de04f6 — Jordan Robinson 8 days ago
initial commit
8 files changed, 660 insertions(+), 0 deletions(-)

A README.md
A index.html
A index.md
A manifest.json
A nav.json
A posts/manifest.json
A posts/post-1.md
A posts/post-2.md
A  => README.md +110 -0
@@ 1,110 @@
# quillhut-static

A minimalist, client-side static site generator that turns Markdown files into a fast, searchable SPA using vanilla JavaScript and Caddy.

## Directory Structure

/opt/www/quillhut-static/       <-- Web Root
├── index.html       			<-- The engine & layout
├── nav.json       			    <-- Header/Footer links
├── index.md      				<-- Homepage content
├── rss.xml          			<-- Generated Feed
├── sitemap.xml      			<-- Generated Sitemap
├── manifest.json      		    <-- Standalone pages (about, index, etc)
├── posts/
│   ├── manifest.json 			<-- List of all post filenames
│   ├── post-1.md     			<-- Individual post
│   └── post-2.md

## Setup & Installation

### 1. Configure Caddy

Your server must handle "fallback" routing so that if a user refreshes `/posts/my-story`, Caddy knows to serve `index.html`.

#### Caddyfile Example

```bash
yourdomain.com {
    root * /opt/www/quillhut-static/ 
    file_server
    
    # Metadata headers
    header /rss.xml Content-Type "application/rss+xml; charset=utf-8"
    header /sitemap.xml Content-Type "application/xml; charset=utf-8"

    # SPA Routing
    try_files {path} /index.html
}
```

### 2. Configure `nav.json`

```json
{
  "header": [
    { "name": "home", "path": "/" },
    { "name": "posts", "path": "/posts" }
  ],
  "footer": [
    { "name": "source", "path": "https://sourcehut.org", "external": true }
  ]
}
```

### 3. Create a Post

Posts live in `/posts/`. Use YAML-like frontmatter:

```markdown
---
title: My First Post
date: 2024-05-22
tags: tech, web
author: linuxgoose
---
Post content goes here...
```

### 4. Update the Manifest

Add the filename to `posts/manifest.json`:

```json
["post-1.md", "post-2.md"]
```

## Admin Workflow

Because this is a static site without a database, robots (like RSS readers or Google) cannot see the content generated by JavaScript. You must manually generate the "static" XML files when you publish new content.
Generating RSS & Sitemap

1. Open your website in a browser.

2. Open the Developer Console (`F12` or `Ctrl+Shift+I`).

3. Type the following command and hit Enter:

```bash
generateRSS(); generateSitemap();
```

4. Two files (rss.xml and sitemap.xml) will download to your computer.

5. Upload these files to your server's root directory.

## Features

- Zero Build Step: Just upload Markdown and refresh.

- Tag Filtering: Automatic tag cloud generation from post metadata.

- Clean URLs: No .html extensions required. (set `USE_CLEAN_PATHS` to *true*)

- Dark Mode: Automatic support based on system preferences.

- Lightweight: Powered by marked.js and vanilla JS.

## License

Open source under the MIT License. Contributions welcome.
\ No newline at end of file

A  => index.html +464 -0
@@ 1,464 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <base href="/"> 
    <title>quillhut-static</title>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <style>
        :root {
            --bg: #ffffff; --text: #111; --gray: #555555; --border: #cccccc;
            --accent: #00B8D9; --code-bg: #f4f4f4; --premium: #d32f2f;
        }
        @media (prefers-color-scheme: dark) {
            :root {
                --bg: #1f2128; --text: #f8f9fa; --gray: #999; --border: #444; --code-bg: #2d2d2d;
            }
        }
        html, body { height: 100%; margin: auto; }
        body { 
            background-color: var(--bg); color: var(--text); display: flex; flex-direction: column;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            max-width: 960px; padding: .5rem 1rem 0; line-height: 1.5;
        }
        a { color: #357edd; text-decoration: none; transition: color .15s ease-in; }
        a:hover { text-decoration: underline; }
        @media(prefers-color-scheme: dark) { a { color: #5ca4f8; } }

        nav { display: flex; flex-direction: column; justify-content: space-between; padding: .25rem; }
        @media screen and (min-width: 48em) { nav { flex-direction: row-reverse; } }
        
        nav ul { display: inline-flex; font-size: .825rem; list-style: none; margin: 0; padding: 0; color: var(--gray); }
        nav ul li:not(:first-child)::before { content: "|"; margin: 0 .25rem; }
        
        .breadcrumbs { display: inline-flex; align-items: baseline; }
        .brand { font-weight: bold; color: var(--text) !important; }
        .context .title::before { color: var(--accent); content: "›"; margin: 0 .25rem; font-size: 1rem; }
        .context .title span { font-size: .825rem; }

        .tag-cloud { margin: 1rem 0; display: flex; gap: 0.5rem; flex-wrap: wrap; }
        .tag { 
            padding: 0.2rem 0.6rem; background: var(--code-bg); border: 1px solid var(--border); 
            font-size: 0.8rem; cursor: pointer; border-radius: 3px; transition: 0.2s;
        }
        .tag.active { background: var(--accent); color: white; border-color: var(--accent); }
        .post-listing { margin-bottom: 1.5rem; border-bottom: 1px solid var(--border); padding-bottom: 1rem; }
        
        .post-info-subtle { 
            font-size: 0.85rem; color: var(--gray); margin-bottom: 1.5rem; 
            display: flex; gap: 10px; align-items: center; opacity: 0.8;
        }
        .post-info-subtle .post-tags-list { display: flex; gap: 8px; }
        .post-info-subtle .post-tag-item { color: var(--accent); }

        main { flex-grow: 1; padding: .25rem; }
        h1, h2, h3 { font-weight: 500; line-height: 1.2; margin-bottom: .5rem; }
        hr { border: 0; border-top: 1px solid var(--border); margin: 1rem 0; }
        
        pre { background: var(--code-bg); padding: 1rem; overflow-x: auto; border: 1px solid var(--border); }
        code { font-family: monospace; background: var(--code-bg); padding: 0.2rem; }

        footer { color: var(--gray); font-size: .75rem; margin-top: auto; padding: 1rem 0; border-top: 1px solid var(--border); }
        .footer-links li { display: inline; }
        .footer-links li:not(:last-child)::after { content: "|"; margin: 0 .5rem; }

        /* DEBUG OVERLAY STYLES */
        #debug-log { 
            position: fixed; bottom: 10px; right: 10px; background: rgba(0,0,0,0.85); color: #00ff00; 
            font-family: monospace; font-size: 11px; padding: 10px; max-width: 350px; 
            z-index: 9999; border-radius: 5px; border: 1px solid #444; pointer-events: none;
            max-height: 250px; overflow-y: auto; box-shadow: 0 0 10px rgba(0,0,0,0.5);
        }
        .log-entry { margin-bottom: 4px; border-bottom: 1px solid #333; padding-bottom: 2px; }
        .log-error { color: #ff5555; }
        .log-warn { color: #ffff55; }
    </style>
</head>
<body>

<div id="debug-log"></div>

<header>
    <nav>
        <div class="session">
            <ul id="header-nav"></ul>
        </div>
        <div class="breadcrumbs">
            <a class="brand" href="/">~quillhut-static</a>
            <div class="context">
                <span class="title"><span id="bc-name">index</span></span>
            </div>
        </div>
    </nav>
    <hr style="display:block; border:0; border-top:1px solid var(--border); margin: 0.5rem 0 1.5rem 0;">
</header>

<main id="content"></main>

<footer style="display: flex">
    <div style="text-align: left;">
        <p>Published with quillhut - an open source static site generator. You can contribute on <a href="https://git-sh.linuxgoose.com/~linuxgoose/quillhut-static">sourcehut</a> (v0.0.1)</p>
    </div>
    <div style="text-align: right; margin-left: auto;">
        <ul id="footer-nav" class="footer-links" style="list-style:none; padding:0; margin:0;"></ul>
    </div>
</footer>

<script>
    const CONFIG = {
        USE_CLEAN_PATHS: true,
        POSTS_DIR: 'posts/',
        MANIFEST: 'posts/manifest.json',
        NAV_JSON: 'nav.json',
        DEBUG_MODE: false // Set to true to see logs and a green box overlay
    };

    const contentDiv = document.getElementById('content');
    const bcName = document.getElementById('bc-name');
    const debugDiv = document.getElementById('debug-log');
    let allPosts = [];
    let activeTags = new Set();

    // Hide debug box immediately if flag is false
    if (!CONFIG.DEBUG_MODE && debugDiv) {
        debugDiv.style.display = 'none';
    }

    // LOGGING UTILITY - Handles both UI and Console
    function log(msg, type = 'info') {
        if (!CONFIG.DEBUG_MODE) return; // Exit early if debug is off

        if (debugDiv) {
            const entry = document.createElement('div');
            entry.className = `log-entry log-${type}`;
            entry.innerText = `[${new Date().toLocaleTimeString()}] ${msg}`;
            debugDiv.appendChild(entry);
            debugDiv.scrollTop = debugDiv.scrollHeight;
        }
        console.log(`DEBUG [${type}]: ${msg}`);
    }

    async function tryFetch(file) {
        const url = file.startsWith('/') ? file : '/' + file;
        log(`Fetching: ${url}`);
        
        try {
            const r = await fetch(url);
            if (!r.ok) throw new Error(`HTTP ${r.status}`);
            
            const text = await r.text();
            
            if (text.trim().startsWith('<!DOCTYPE html>')) {
                log(`Rejected: ${url} returned HTML`, 'warn');
                throw new Error("Invalid file content (HTML returned)");
            }
            return text;
        } catch (e) {
            log(`Fetch failed: ${url} (${e.message})`, 'error');
            throw e;
        }
    }

    function formatURL(path, isExternal) {
        if (isExternal) return path;
        const cleanPath = path.replace(/\.md$/, ''); 
        return CONFIG.USE_CLEAN_PATHS ? `/${cleanPath}` : `?p=${cleanPath}`;
    }

    async function buildNav() {
        try {
            const data = JSON.parse(await tryFetch(CONFIG.NAV_JSON));
            document.getElementById('header-nav').innerHTML = data.header.map(item => 
                `<li><a href="${formatURL(item.path, item.external)}">${item.name}</a></li>`
            ).join('');
            document.getElementById('footer-nav').innerHTML = data.footer.map(item => 
                `<li><a href="${formatURL(item.path, item.external)}">${item.name}</a></li>`
            ).join('');
            log("Navigation menu built");
        } catch (e) { log("Nav failed to load", 'warn'); }
    }

    function parseFrontmatter(text) {
        const regex = /^---\s*\n([\s\S]*?)\n---\s*\n/;
        const match = text.match(regex);
        const meta = {};
        let content = text;
        if (match) {
            content = text.replace(regex, '');
            match[1].split('\n').forEach(line => {
                const [key, ...val] = line.split(':');
                if (key && val.length) meta[key.trim()] = val.join(':').trim();
            });
        }
        return { meta, content };
    }

    async function render() {
        const params = new URLSearchParams(window.location.search);
        let route = CONFIG.USE_CLEAN_PATHS ? window.location.pathname : params.get('p');

        if (!route || route === '/' || route === '/index.html' || route === 'index') {
            route = 'index';
        }

        route = route.replace(/^\/+|\/+$/g, '');
        log(`Route detected: ${route}`);
        bcName.innerText = route;

        if (route === 'posts') {
            await renderPostsPage();
            return;
        }

        try {
            const fileName = route.endsWith('.md') ? route : `${route}.md`;
            let rawMd;
            try {
                log(`TRYING SUBFOLDER: ${CONFIG.POSTS_DIR}${fileName}`);
                rawMd = await tryFetch(CONFIG.POSTS_DIR + fileName);
            } catch (e) {
                log(`TRYING ROOT: ${fileName}`);
                rawMd = await tryFetch(fileName);
            }
            
            const { meta, content } = parseFrontmatter(rawMd);
            
            let metaHtml = '';
            if (meta.date || meta.tags) {
                const tagList = meta.tags ? meta.tags.split(',').map(t => `<span class="post-tag-item">#${t.trim()}</span>`).join(' ') : '';
                metaHtml = `<div class="post-info-subtle">${meta.date ? `<span>${meta.date}</span>` : ''}${meta.date && meta.tags ? '<span>|</span>' : ''}<div class="post-tags-list">${tagList}</div></div>`;
            }

            contentDiv.innerHTML = metaHtml + marked.parse(content);
            document.title = meta.title ? `${meta.title} | quillhut-static` : `${route} | quillhut-static`;
            log("Content rendered successfully");
            
        } catch (err) {
            log(`FATAL: Could not render ${route}. ${err.message}`, 'error');
            await render404(route);
        }
    }

    async function renderPostsPage() {
        log("Loading Posts list...");
        let pageTitle = "Posts";
        let pageIntro = "";

        try {
            const raw = await tryFetch('posts.md');
            const { meta, content } = parseFrontmatter(raw);
            if (meta.title) pageTitle = meta.title;
            if (content.trim()) pageIntro = marked.parse(content);
        } catch (e) { log("Optional posts.md info not found", 'info'); }

        bcName.innerText = pageTitle.toLowerCase();
        document.title = `${pageTitle} | quillhut-static`;
        
        contentDiv.innerHTML = `<h1>${pageTitle}</h1>${pageIntro}<div class="tag-cloud" id="tag-cloud"></div><div id="posts-container">Loading posts...</div>`;
        
        try {
            const manifestRaw = await tryFetch(CONFIG.MANIFEST);
            const manifest = JSON.parse(manifestRaw).filter(f => !f.startsWith('.'));
            allPosts = await Promise.all(manifest.map(async file => {
                const raw = await tryFetch(CONFIG.POSTS_DIR + file);
                const { meta } = parseFrontmatter(raw);
                return { ...meta, file: file.replace('.md', ''), tags: meta.tags ? meta.tags.split(',').map(t => t.trim()) : [] };
            }));
            updateDisplay();
            log(`Manifest loaded: ${allPosts.length} posts found`);
        } catch (e) { 
            log(`Manifest error: ${e.message}`, 'error');
            contentDiv.innerHTML += "<p>Error loading manifest.</p>"; 
        }
    }

    function updateDisplay() {
        const container = document.getElementById('posts-container');
        const cloud = document.getElementById('tag-cloud');
        const uniqueTags = [...new Set(allPosts.flatMap(p => p.tags))].sort();
        cloud.innerHTML = uniqueTags.map(t => `<span class="tag ${activeTags.has(t) ? 'active' : ''}" onclick="toggleTag('${t}')">${t}</span>`).join('');
        const filtered = allPosts.filter(p => activeTags.size === 0 || p.tags.some(t => activeTags.has(t)));
        container.innerHTML = filtered.length ? filtered.map(p => `
            <div class="post-listing">
                <div class="post-meta">${p.date || 'No Date'} • By ${p.author || 'Anonymous'}</div>
                <h3><a href="${formatURL(p.file, false)}">${p.title || p.file}</a></h3>
                <div style="margin-top:5px">${p.tags.map(t => `<small style="color:var(--accent);margin-right:8px">#${t}</small>`).join('')}</div>
            </div>
        `).join('') : '<p>No matches found.</p>';
    }

    window.toggleTag = (tag) => {
        activeTags.has(tag) ? activeTags.delete(tag) : activeTags.add(tag);
        updateDisplay();
    };

    async function render404(file) {
        try {
            const errorMd = await tryFetch('404.md');
            const { content } = parseFrontmatter(errorMd);
            contentDiv.innerHTML = marked.parse(content);
        } catch {
            contentDiv.innerHTML = `<h1>404</h1><p>Path <code>${file}</code> not found.</p>`;
        }
        bcName.innerText = "404";
    }

    document.addEventListener('click', e => {
        const link = e.target.closest('a');
        if (CONFIG.USE_CLEAN_PATHS && link && link.href.startsWith(window.location.origin) && !link.getAttribute('target')) {
            const url = new URL(link.href);
            const hasExtension = url.pathname.includes('.') && !url.pathname.endsWith('.md');
            if (!hasExtension) {
                e.preventDefault();
                log(`Link clicked: ${url.pathname}`);
                history.pushState(null, '', link.href);
                render();
            }
        }
    });

    async function generateRSS() {
        log("Generating full RSS feed...");
        try {
            const manifestRaw = await tryFetch(CONFIG.MANIFEST);
            // Exclude index.md and any hidden files
            const manifest = JSON.parse(manifestRaw).filter(f => !f.startsWith('.') && f !== 'index.md');
            
            const feedItems = [];

            for (const file of manifest) {
                try {
                    const rawMd = await tryFetch(CONFIG.POSTS_DIR + file);
                    
                    // 1. BLOCK ACCIDENTAL HTML INJECTION
                    // If Caddy lied and gave us index.html, this stops the XML from breaking
                    if (rawMd.trim().toLowerCase().startsWith('<!doctype html')) {
                        log(`Skipping ${file}: Request returned HTML index instead of Markdown`, 'warn');
                        continue;
                    }

                    const { meta, content } = parseFrontmatter(rawMd);
                    
                    // 2. RENDER HTML
                    const htmlContent = marked.parse(content);
                    const slug = file.replace('.md', '');
                    const url = `${window.location.origin}${formatURL(slug, false)}`;
                    
                    // 3. CATEGORIES/TAGS
                    const tagsArray = meta.tags ? meta.tags.split(',').map(t => t.trim()) : [];
                    const categoryTags = tagsArray.map(t => `<category><![CDATA[${t}]]></category>`).join('\n            ');
                    
                    // 4. BUILD ITEM WITH CDATA
                    // CDATA tells the RSS reader: "Treat everything inside as raw text/html, don't try to parse it as XML"
                    feedItems.push(`
            <item>
                <title><![CDATA[${meta.title || slug}]]></title>
                <link>${url}</link>
                <guid isPermaLink="true">${url}</guid>
                <pubDate>${new Date(meta.date || Date.now()).toUTCString()}</pubDate>
                ${categoryTags}
                <description><![CDATA[${htmlContent}]]></description>
            </item>`);
                } catch (e) {
                    log(`Failed to include ${file}: ${e.message}`, 'warn');
                }
            }

            const rssTemplate = `<?xml version="1.0" encoding="UTF-8" ?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title><![CDATA[quillhut-static]]></title>
        <link>${window.location.origin}</link>
        <description><![CDATA[Latest posts from quillhut-static]]></description>
        <language>en-us</language>
        <atom:link href="${window.location.origin}/rss.xml" rel="self" type="application/rss+xml" />
        <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
        ${feedItems.join('')}
    </channel>
    </rss>`;

            const blob = new Blob([rssTemplate], { type: 'application/xml' });
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = 'rss.xml';
            link.click();
            log("Fixed RSS feed generated.");
        } catch (err) {
            log(`Failed to generate RSS: ${err.message}`, 'error');
        }
    }

    async function generateSitemap() {
        log("Generating sitemap.xml from manifests...");
        try {
            // 1. Fetch both manifests
            const [rootManifestRaw, postManifestRaw] = await Promise.all([
                tryFetch('manifest.json'),
                tryFetch(CONFIG.MANIFEST)
            ]);

            const rootFiles = JSON.parse(rootManifestRaw).map(f => ({ path: f, isPost: false }));
            const postFiles = JSON.parse(postManifestRaw).map(f => ({ path: CONFIG.POSTS_DIR + f, isPost: true }));
            
            const allFiles = [...rootFiles, ...postFiles];

            // 2. Map files to URL entries
            const urlEntries = await Promise.all(allFiles.map(async (fileObj) => {
                try {
                    const rawMd = await tryFetch(fileObj.path);
                    if (rawMd.trim().toLowerCase().startsWith('<!doctype html')) return '';

                    const { meta } = parseFrontmatter(rawMd);
                    
                    // Extract filename for slugging
                    const fileName = fileObj.path.split('/').pop();
                    const slug = fileName.replace('.md', '');
                    
                    // Logic: index.md in root is "/"
                    // post-1.md in posts/ is "/post-1"
                    // about.md in root is "/about"
                    const urlPath = (slug === 'index' && !fileObj.isPost) ? '' : formatURL(slug, false);
                    const fullUrl = `${window.location.origin}${urlPath}`;
                    
                    return `
        <url>
            <loc>${fullUrl}</loc>
            <lastmod>${meta.date || new Date().toISOString().split('T')[0]}</lastmod>
            <changefreq>${slug === 'index' ? 'daily' : 'monthly'}</changefreq>
            <priority>${slug === 'index' ? '1.0' : '0.8'}</priority>
        </url>`;
                } catch (e) {
                    return '';
                }
            }));

            const sitemapTemplate = `<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
        ${urlEntries.filter(u => u !== '').join('')}
    </urlset>`;

            const blob = new Blob([sitemapTemplate], { type: 'application/xml' });
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = 'sitemap.xml';
            link.click();
            log("sitemap.xml generated via manifests.");
        } catch (err) {
            log(`Failed to generate sitemap: ${err.message}`, 'error');
        }
    }

    async function generateAllMetadata() {
        await generateRSS();
        await generateSitemap();
        log("All metadata files updated!");
    }

    buildNav();
    render();
    window.onpopstate = render;
</script>

</body>
</html>
\ No newline at end of file

A  => index.md +22 -0
@@ 1,22 @@
# Hello, World!

Welcome to my personal corner of the web. This site is powered by **quillhut**, a minimalist static site engine that renders Markdown directly in your browser.

### About this site
This is a boilerplate example of the `index.md` file. It serves as the homepage for the SPA (Single Page Application). 

* **Fast:** No heavy frameworks, just vanilla JS.
* **Markdown-first:** Write in plain text, and it just works.
* **Clean:** No trackers, no ads, just content.

### Recent Projects
You can find my latest writing in the [posts](/posts) section. I focus on:
1.  **Minimalist Web Design**
2.  **Linux & Open Source**
3.  **Static Site Architectures**

---

> "Simplicity is the ultimate sophistication." — Leonardo da Vinci

If you want to reach out, feel free to check my [security.txt](/security.txt) for contact details or verify my identity via my [PGP key](/pgp-key.txt).
\ No newline at end of file

A  => manifest.json +4 -0
@@ 1,4 @@
[
  "index.md",
  "posts.md"
]
\ No newline at end of file

A  => nav.json +9 -0
@@ 1,9 @@
{
    "header": [
        { "name": "Home", "path": "index" },
        { "name": "Journal", "path": "posts" }
    ],
    "footer": [
        { "name": "source", "path": "https://sourcehut.org", "external": true }
    ]
}
\ No newline at end of file

A  => posts/manifest.json +4 -0
@@ 1,4 @@
[
"post-1.md", 
"post-2.md"
]
\ No newline at end of file

A  => posts/post-1.md +26 -0
@@ 1,26 @@
---
title: Why I Built My Own SSG
date: 2026-01-10
tags: web, javascript, minimalism
author: linuxgoose
---

# Why I Built My Own SSG

I wanted a blog that didn't require a complex build pipeline or a 500MB `node_modules` folder. By using **vanilla JavaScript** and **Caddy**, I created a system where the browser does the heavy lifting.

### The Technical Stack
- **Engine:** Vanilla JS Router
- **Parser:** [Marked.js](https://marked.js.org/)
- **Server:** Caddy with SPA routing
- **Metadata:** YAML-style frontmatter

### Code Example
Here is how I handle the routing logic in my `index.html`:

```javascript
function render() {
    let route = window.location.pathname;
    // logic to fetch and render markdown
}
```
\ No newline at end of file

A  => posts/post-2.md +21 -0
@@ 1,21 @@
---
title: The Joy of Small Web
date: 2026-01-12
tags: philosophy, web
author: linuxgoose
---

# The Joy of Small Web

The modern internet is bloated. Websites are often several megabytes just to display a few paragraphs of text. The **Small Web** movement is about reclaiming the simplicity of the early internet.

### What makes a "Small" website?
1. **No Tracking:** Respecting user privacy by default.
2. **Text-Heavy:** Prioritizing information over high-res hero images.
3. **No Frameworks:** Reducing the "tax" paid by the user's CPU.

When you browse this site, you aren't loading a React bundle. You are loading a single HTML file and a few Markdown strings. It feels **instant** because it is.

---

*If you enjoyed this, check out my other posts in the [archive](/posts).*
\ No newline at end of file