One page, two visits

J
John Doe
|
Jan 15, 2024
|
5 min read
#react
#javascript
#frontend

Yesterday the platform learned to serve customer-owned domains. The settings dialog now hands you a load balancer IP, walks you through pointing your domain at it, and manages the certificate. So today I am going to watch a request arrive from the outside and become a page, then watch the site's owner save a change, then watch the next request prove the change landed. One page, two visits, and the entire multitenant serving story in between.

Giant Context, the platform I build, hosts every site it builds. Hosting them is a job with two goals that are hard to satisfy at once. Every domain must find exactly its own site among all of them, and the page must arrive fast enough that nobody suspects a platform is underneath.

The domain dialog shipped yesterday. One load balancer IP, and a managed certificate the platform handles.

Visit one, a stranger arrives

Somewhere, a person types one of my customers' domains and hits enter. They have never heard of Giant Context, and nothing that follows should change that.

DNS resolves it to one IP address, the same load balancer IP that dialog hands every customer. This is the first multitenant fact. There is one address for every site on the platform. Ten thousand customers will not get ten thousand servers.

Past the address, the request hits the router, a small service with one job. Read the hostname the browser asked for, and decide which customer's which site this is. That decision is a lookup against what customers have connected, and the lookup is the boundary. A domain that resolves here but belongs to nobody gets nothing. A domain that belongs to someone gets exactly that someone's site, and there is no request shape that gets you someone else's.

Then the page itself. The public sites are served by Next.js, the one place in the stack I kept a framework instead of rolling my own, because server rendering is what it is good at. And the page was not rendered when they asked for it. It was rendered earlier and cached, and the visitor is handed the saved copy. The framework re-renders it in the background at most once a minute if anyone is asking, so a site whose pages rarely change gets served at static-file speed.

The whole crossing, from keystroke to page, spent nearly all its time on network hops and almost none on computation.

The nut

The part that decided whether any of it mattered was whether the caching could be trusted, because the math is unforgiving. The frontend is the only part of the platform a customer's audience ever meets. If it is slow, nothing upstream is valuable, and my rendering layer sits behind a proxy, which adds latency before a single byte of page arrives. The cache is what buys that latency back.

Content decoding failures on the proxy were fixed the week before (2026-02-11 · bd21f1072). Forwarding quirks turned up too. A certificate state bug was fixed the same day the domain dialog shipped (2026-02-18 · 86ad7bbe8). Serving strangers is one small correctness problem after another, and every one of them lives between the customer's work and the customer's audience.

The save

A cached page has one way to fail, which is being wrong. The customer who owns this site opens the console, changes the headline on the page, and clicks save. Somewhere in the cache sits a rendered copy of the old page, still fast and now false.

So save does one more thing. The builder tells the frontend to throw the copy away:

The revalidation route

// app/api/revalidate/route.tsimport { revalidatePath, revalidateTag } from "next/cache";// …

The wire is that direct. Saving in the console calls the rendering layer's revalidation route, the stale copy is dropped, and the next request re-renders from what the API now says. Cache purging has been wired to the builder since the first week the platform could serve a site at all (2025-12-30 · f9e56320d), because a fast wrong page is worse than a slow right one.

A fast wrong page is worse than a slow right one.

Visit two

The next visitor types the same domain. The router makes the same lookup, the cache has no copy, the page renders fresh, and the new headline is there. From here it is cached again, fast again, until the next save.

That round trip, save to stranger, is the product. Everything else the platform does, the builder, the AI, the permissions, funnels into whether a person who types the domain sees the current site quickly.

A stranger arrives

Someone types the domain and hits enter, never having heard of the platform.

One shared IP

DNS resolves every customer domain to the same load balancer address.

The router picks the site

It reads the hostname and maps it to exactly one customer's site.

A cached page, served fast

The page was rendered earlier. The visitor is handed the saved copy.

The owner saves

A headline changes in the console, and the stale copy is dropped.

The next visit renders fresh

The same lookup, no cached copy, and the new headline is there.

What the frontend costs

One more mechanic, because the serving layer is not a job you finish once. Every feature the platform grows, new dialogs, headers, sidebars, localization, cookie banners, has to exist out here too, rendered for strangers, matched to what the console builds. The frontend changes constantly, and it deploys only when its own files change, the same rule every service follows, so each deploy is scoped and proven. The serving layer was proven within the platform's first several weeks. It has not stopped growing since.

Wiring this up yourself

Serving many customers' domains from one system takes three pieces, and none of them are exotic.

One address. A load balancer with one IP that every customer domain points at, and a certificate story you automate from day one, because certificates at any scale are not a hand process. On Google Cloud this is Cloud Load Balancing with managed certificates.

One lookup. A small routing service that maps hostname to tenant to site. Keep it boring, keep it fast, and treat an unknown hostname as nothing rather than an error page that admits the platform exists.

One cache with a kill switch. Next.js Incremental Static Regeneration gives you the cached copies and the background re-render. The part you must build is the wire from your editor's save button to the cache's revalidation, because a cache without invalidation just keeps serving stale pages. The routing is roughly a week. The trust in the cache takes longer, and it is the part worth the time. The page is the product.

Have questions?

If you're interested in my work or Giant Context, contact me!

Contact Jesse
Legal
Privacy Policy
Terms of Service
Cookie Policy

© 2026 Giant Context. All rights reserved.

Multitenant Serving & Caching