This website is faster than light

Click around this site for a bit - you'll notice something unusual.

It feels instant. Pages appear faster than you can even lift your mouse button. Even if you're halfway across the world from my servers, links still open like there's no distance at all. Faster than the speed of light!

Of course, nothing is actually breaking physics. But the experience sure makes it feel like it. At first glance this site looks like a plain static site. It isn't. There are a few tricks under the hood that make it this snappy.

Let's peel back the layers.

"Act on press"

This idea came from a tweet by the legendary programmer John Carmack:

Act on press

This is a UI design hill I will die on, and it dismays me how often and hard I have had to fight for it.

Almost all interaction methods have a “press” and “release” event associated with them. Whenever possible, you should “do the thing” when you get the press event instead of waiting for the release event, because it makes the interaction feel substantially more responsive, and it reduces user errors by not allowing the focus to slide out of the hot box between press and release.

... (see the full tweet)

As someone who plays a lot of fast-paced FPS games, I'm hypersensitive to response time. When browsing the web, it usually takes me about ~100-150ms just to release the mouse button. By switching link behavior from "navigate on mouse release" to "navigate on mouse down", I instantly save 100-150ms of perceived latency. For context, a half-globe round trip is ~300ms, so this little change basically cuts latency in half.

To do this, I built a <FastA/> component - a tiny wrapper around the normal <a/> tag - that fires on press instead of release (the code is truncated here, but the full source is linked at the end of this post):

// This could probably be implemented in a better way,
// shoot me an email if you know a better way :)
#[component]
pub fn FastA<H>(
    // truncated

    view! {
        <a
            href=path.clone()
            target=target
            class=class.map(Oco::from)
            on:mousedown=move |event| {
                if is_left_click(&event) {
                    event.prevent_default();
                    navigate(&path, Default::default());
                }
            }
        >
            {children()}
        </a>
    }
}

It can be used as a replacement for <a/>, like so:

-<a href="/contact">"Contact"</a>
+<FastA href="/contact">"Contact"</FastA>

The on:mousedown event still degrades gracefully - if the user's browser doesn't support WASM, it just falls back to the normal behavior.

Did I say WASM?

Rust, Leptos, and zero network trips

As you may notice, the code above looks like Rust - and that's because it is. The site is built with Leptos, a reactive web framework for Rust.

I have a markdown_to_html() function that reads Markdown content and outputs HTML, adds syntax highlighting, and so on. Then there are functions to list_posts() and get_post(). You know, standard blog stuff.

The twist is that the same function runs on both the backend and the frontend. When you click a post, the get_post() function returns the content. If WASM is supported, it runs right in the browser. If not, it runs on the server.

But doesn't that still require network round trips to get the post from the server? Short answer: nope - if your browser supports WASM.

Here's where it gets fun: because the Leptos framework is isomorphic, I can bake all the post content directly into both the server-side Linux binary and the client-side WASM binary - while still sharing the exact same logic on both ends.

For a while, I was just tossing raw Markdown in with include_str and include_dir, then parsing it at runtime using pulldown-cmark and syntect. It worked, but it made the binaries chunky and wasted CPU cycles. Now I render everything at compile time using the same libraries - basically like a static site generator.

To pull that off, I use a build.rs script that runs at compile time. It reads all the Markdown files, converts them to HTML, adds syntax highlighting, and then bakes everything into a single static array.

fn main() {
    let dest_path = Path::new(&out_dir).join("posts_data.rs");

    let entries: Vec<String> = POSTS
        .map(|(date, id)| {
            let markdown = fs::read_to_string(format!("./content/posts/{id}.md"))
            // truncated
                markdown_to_html(&markdown)
            )
        })
        .collect();

    fs::write(
        dest_path,
        format!(
            "static POSTS: &[(&str, &str, &str, &str)] = &[\n{}\n];",
            entries.join("\n")
        ),
    )
}

This spits out a posts_data.rs file with a static POSTS array, which then gets baked into the binaries at compile time. From there, the application logic can read the rendered posts with functions like these:

include!(concat!(env!("OUT_DIR"), "/posts_data.rs"));

pub fn list_posts() -> Vec<PostMetadata> {
    POSTS
        .iter()
        // truncated
        .collect()
}

pub fn get_post(path: String) -> Option<(PostMetadata, String)> {
    POSTS
        .iter()
        .filter_map(|(id, title, date, content)| {
            // truncated
        })
        .next()
}

The result:

There's one catch: since the content is baked right into the binary, the data transfer can get heavier if I ever decide to publish a mountain of posts. But let's be honest - I'm way too lazy to write that many. At the time of writing, the WASM binary is only about 150KB compressed.

Squeezing the WASM binary

Besides rendering all Markdown at compile time to avoid extra runtime dependencies, we can shrink the WASM binary even further using some compiler flags in Cargo.toml:

[profile.wasm-release]
opt-level = 'z'
lto = true
codegen-units = 1
panic = "abort"

This is also covered in the Leptos book, but I'll break down those options a bit more:

What's next

While the site is very fast once the WASM binary is loaded, there are still ways to improve the initial page load - such as adding more nodes in different regions or tuning CDN caching. But since my self-hosting setup is already a bit over-engineered, I'll save that for another weekend.

Stay tuned for a future post on how I host my blog on a highly available, multi-region, multi-cloud hybrid-metal self-hosting platform with an IPv6-only WireGuard mesh and $RANDOM_BUZZWORD! (no, I'm not joking)

All source code for the above can be found at my blog repo (permalink, mirror)