Appearance
How the WL side works
The WL pair — a Next.js app and a Laravel API on 178.128.113.107 — serves every customer event. Which event a request belongs to is decided by its Host; see Domain resolution for that half. This page covers what happens afterwards.
The runtime
Apache terminates TLS and does not send X-Forwarded-Proto. That has bitten this codebase before: building a URL from req.url yields the proxy's internal origin, so redirects from middleware went to http://localhost:3000. The public origin is derived from the Host header instead — see publicOrigin() in middleware.ts.
Middleware runs on the Edge runtime, so it uses plain fetch() and cannot import the React-cache helpers in lib/domain.ts. That is why the host cache is duplicated there rather than shared.
Rendering a page
If resolution produced nothing, the layout catches DomainNotConnectedError and renders the "event not connected" page rather than a 500.
Pages are data
Most of what a participant sees is a block tree built in the admin page builder and stored per page. The frontend has a renderer per block type — currently around 84 of them — and draws whatever the tree contains.
Consequences worth internalising before changing anything here:
- A new block type needs a renderer on both sides. The admin's catalogue and the frontend's registry are separate lists, and they drift.
- Container types must be wired into both recursions. A new container missing from either
ContainerChildrenorColumnsChildrenmakes its nested contents vanish from the tree view. - Blocks using hooks need
"use client". Omitting it fails at build, not at review. - CSS injected through
dangerouslySetInnerHTMLmust not contain a backtick — not even inside a comment. It terminates the template literal and breaks the build with an error nowhere near the cause.
Tokens
Copy is written with tokens that resolve from real configuration — {{event.name}}, {{share_text}}, {{total_raised}}.
Unresolved tokens render empty, not loudly
There is no error. You get a sentence with a hole in it, on a live event. Tokens must resolve from actual configuration rather than hardcoded fallbacks, and blocks should be gated on data presence so a block with nothing behind it hides instead of rendering an empty shell.
Custom HTML is client-only
The custom_html block is sanitised with DOMPurify client-side only — there is no server rendering of raw HTML. Its CSS is scoped under cb_<blockId>, and scripts are stripped.
Anything that needs JavaScript has to be a native block. That is precisely why blocks like early_bird_slider exist rather than being pasted-in HTML.
Caching
| Cache | TTL | Notes |
|---|---|---|
| Host → event id | 60s | Middleware, in memory |
| Event config, pages, meta | 60s | lib/serverCache, per event |
/resolve-event response | 60s | Cache-Control header |
A hard reload sends Cache-Control: no-cache, which bypasses the server TTL cache. A normal reload does not — so an editor checking their change should hard-reload rather than conclude nothing saved.
The API
Laravel, at wl-api.togoparts.com, mostly public read endpoints plus the authenticated participant surface.
Two rate-limiting notes, both learned the hard way:
/resolve-eventuses thepublic-readlimiter, not the defaultapione. At 60/minute a single active visitor exhausted it, and the 429s surfaced as the not-connected page on live events.- Client error reports are tightly limited per IP; a real browser sends at most one per crash.
Participant authentication layers over the legacy Togoparts user table, verified read-only. Signup provisions a tgp_userid. See Legacy app and TGP.
One deployment, every event
The thing to keep in mind whenever you change this codebase:
There is no gradual rollout
One process serves every live event. A change to tga-v3-wl-web reaches all of them simultaneously. Anything conditional on configuration must default safely for events that never set it — including events that were configured a year ago and are still running.
Read next
- Domain resolution — how the event was chosen
- WL frontend and WL API — the full service docs
- Multi-version hosting — v3 and v4 side by side