Hoppa till innehållet

post.md

23 okt. 2026 · 3 min läsning
Animerad ASCII-konst av en Nacra 17-katamaran som seglar över havet i solnedgången

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.

Bloggens layout: en inläggslista till vänster och det öppna inlägget till högerBloggen 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:

kortkommandon
1Öppna startsidanglobal
2Öppna bloggenglobal
jScrolla nedglobal
kScrolla uppglobal
g gHoppa till börjanglobal
GHoppa till slutetglobal
nÄldre inläggblogg
pNyare inläggblogg
dByt temaglobal
?Visa kortkommandonglobal

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:

src/features/blog/embeds/index.tsx
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:

LagerMappAnsvar
Rutterapp/Sidor, metadata, ikoner och bilder
Gränssnittcomponents/tui/Paneler, navigeringsfält, statusrad, tangentbindningar
Funktionerfeatures/Startsidans sektioner och bloggen
Verktyglib/, hooks/Metadata, observerbarhet, tangentinmatning

Så här många källfiler finns i varje mapp när detta skrivs:

Källfiler per mapp
Visa data
folderfiles
app17
components16
lib16
features15
i18n4
hooks3
types1

Genererade ikoner och delningskort

Next.js 16 bygger metadatabilder från filer i katalogen app/:

  • icon.tsx och apple-icon.tsx ritar favicon och iOS-ikon från en enda SVG, assets/logo-icon.svg.
  • opengraph-image.tsx ritar 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:

src/lib/observability/server.ts
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.

NORMAL~/blog/how-this-site-is-built
0%

Kortkommandon

12
Huvudnavigering
jk
Scrolla ned / upp
g gG
Hoppa till början / slutet
np
Nyare / äldre inlägg
d
Byt tema
:
Öppna kommandopaletten
?
Visa eller dölj hjälpen
esc