post.md
23 okt. 2026 · 3 min läsning
Så är den här webbplatsen byggd
En kort rundtur i arkitekturen bakom cmkrw.com, från terminallayouten till Markdown, diagram och felrapportering.
- nextjs
- design
Innehåll
Den här webbplatsen är liten med flit: en sida, en blogg och så lite JavaScript som möjligt. Så här hänger delarna ihop, om du vill bygga något liknande.
Terminalen är layouten
Webbplatsen är uppbyggd som en terminalapp. Ett flikfält löper längst upp, en statusrad sitter längst ner, och allt däremellan är paneler: rutor med ram där rubriken sitter i överkanten. En liten komponent, Pane, ritar dem alla.
Bloggen som master–detalj-vy: listan ligger kvar medan inlägget byts.
Navigeringen följer samma idé. I bloggen ligger inläggslistan kvar till vänster medan inlägget öppnas till höger, så man behöver aldrig en tillbaka-länk för att bläddra mellan inlägg. Kortkommandona fungerar som i en terminal, och du kan sortera den här tabellen genom att klicka på en kolumnrubrik:
| 1 | Öppna startsidan | global |
| 2 | Öppna bloggen | global |
| j | Scrolla ned | global |
| k | Scrolla upp | global |
| g g | Hoppa till början | global |
| G | Hoppa till slutet | global |
| n | Äldre inlägg | blogg |
| p | Nyare inlägg | blogg |
| d | Byt tema | global |
| ? | Visa kortkommandon | global |
Rörelserna är mest CSS. Kommandot i hjälten skrivs ut med en steps()-animation, paneler glider in när de scrollas till, och både linjen under flikfältet och procenttalet i statusraden följer scrollpositionen. Allt stängs av under prefers-reduced-motion.
Att skriva inlägg
Inlägg är vanliga Markdown-filer i content/blog/<slug>/<locale>.md, renderade med TanStack Markdown. En saknad översättning faller tillbaka på den engelska filen, och draft: true håller ett inlägg utanför produktionsbyggen.
Tre delar av TanStack sköter resten:
- Markdown tolkar och renderar inlägget som React-element, utan någon HTML-sträng emellan.
- Highlight färglägger kodblock. Bara de språk jag registrerar paketeras, och färgerna kommer från samma CSS-variabler som resten av temat.
- Charts och Table bäddas in med en enradskommentar som pekar ut en JSON-fil bredvid inlägget.
Kodblock kan ha en filrubrik:
export function createEmbeds(slug: string, locale: string) {
return {
chart: ({ data = "", kind = "bar", x = "x", y = "y", title = "Chart" }) => (
<TuiChart
rows={getPostDataset(slug, data, locale)}
kind={kind}
x={x}
y={y}
label={title}
/>
),
}
}Vanliga Markdown-tabeller fungerar också och får samma utseende:
| Lager | Mapp | Ansvar |
|---|---|---|
| Rutter | app/ | Sidor, metadata, ikoner och bilder |
| Gränssnitt | components/tui/ | Paneler, navigeringsfält, statusrad, tangentbindningar |
| Funktioner | features/ | Startsidans sektioner och bloggen |
| Verktyg | lib/, hooks/ | Metadata, observerbarhet, tangentinmatning |
Så här många källfiler finns i varje mapp när detta skrivs:
Visa data
| folder | files |
|---|---|
| app | 17 |
| components | 16 |
| lib | 16 |
| features | 15 |
| i18n | 4 |
| hooks | 3 |
| types | 1 |
Genererade ikoner och delningskort
Next.js 16 bygger metadatabilder från filer i katalogen app/:
icon.tsxochapple-icon.tsxritar favicon och iOS-ikon från en enda SVG,assets/logo-icon.svg.opengraph-image.tsxritar delningskortet för startsidan, och ett till ritar ett kort för varje inlägg.
Det finns ingen favicon.tsx. Dokumentationen är tydlig med att en favicon inte kan genereras, så icon.tsx gör det jobbet. Allt detta delar en liten uppsättning byggstenar, så att ändra varumärkesmärket ändrar favicon och alla delningskort på en gång.
Instrumentering
Två filer sköter observerbarheten. instrumentation.ts körs på servern och rapporterar förfrågningsfel. instrumentation-client.ts körs före hydrering och spårar klientfel, webbvitaler och ruttbyten. Båda skickar händelser till en rapportör som delar ut dem till mottagare:
export const serverReporter = createReporter([
consoleSink,
...(endpoint ? [endpointSink(endpoint)] : []),
])Klienthändelser skickas till /api/observability, som loggar dem. Att peka rapportören mot en riktig insamlare senare innebär att lägga till en mottagare, utan att röra någon av ingångsfilerna.
Översättningar
Webbplatsen finns på tio språk, inklusive arabiska och hebreiska som läses från höger till vänster. Varje text finns i en meddelandefil per språk; om en nyckel någon gång saknas blandas standardspråkets meddelanden in underifrån, så en sida blir aldrig tom. Inlägg fungerar likadant: varje språk får en egen fil, och saknas den används engelskan.