Code — how this page is built

Under the hood

The actual source of the components on this page, read straight from the repo. Copy it, read it, or open it on GitHub.


Articles indexview on GitHub ↗

A dense 4-column masonry built from getAllArticles() (MDX frontmatter on disk). Tiles are greyscale placeholders that gain colour on hover, with a dot + title caption that fades up. Every-other tile spans two rows for rhythm.

import type { Metadata } from 'next';
import Image from 'next/image';
import Link from 'next/link';

import { Jsonld } from '@/components/Jsonld';
import { PageFrame } from '@/components/shell/PageFrame';
import { getAllArticles } from '@/lib/content';
import type { CodeRef, TaggingData } from '@/lib/lens';
import { normalizeView } from '@/lib/lens';
import { absoluteUrl, pageMetadata, websiteJsonLd, type JsonLd } from '@/lib/seo';

import styles from './articles.module.css';

const TITLE = 'Articles';
const DESCRIPTION =
  'Write-ups from kaspirius — essays on AI, images, software, and cities. Everything is grey until you look at it.';

export const metadata: Metadata = pageMetadata({
  title: TITLE,
  description: DESCRIPTION,
  path: '/articles',
});

const CODE_REFS: CodeRef[] = [
  {
    title: 'Articles index',
    file: 'app/articles/page.tsx',
    explanation:
      'A dense 4-column masonry built from getAllArticles() (MDX frontmatter on disk). Tiles are greyscale placeholders that gain colour on hover, with a dot + title caption that fades up. Every-other tile spans two rows for rhythm.',
  },
  {
    title: 'content layer',
    file: 'lib/content.ts',
    explanation:
      'Reads content/articles/*.mdx, parses frontmatter with gray-matter, and returns sorted Article objects. The same layer feeds the index and the reader.',
  },
];

/** Accent dots cycle through the brand palette; some tiles span two rows. */
const ACCENTS = [
  'var(--accent-red)',
  'var(--accent-olive)',
  'var(--accent-amber)',
  'var(--accent-blue)',
  'var(--accent-rose)',
  'var(--accent-gold)',
  'var(--accent-teal)',
  'var(--accent-violet)',
  'var(--accent-grey)',
];

function ArticlesIndex() {
  const articles = getAllArticles();
  return (
    <div className={styles.root}>
      {/* No visible top bar — kept crawlable for internal linking / GEO. */}
      <nav className="sr-only" aria-label="Sections">
        <Link href="/">kaspirius</Link>
        <Link href="/home">Home</Link>
        <Link href="/content">Content</Link>
      </nav>

      <div className={styles.gridWrap}>
        <div className={styles.grid}>
          {articles.map((a, i) => {
            const accent = ACCENTS[i % ACCENTS.length];
            const tall = i % 3 === 0;
            return (
              <Link
                key={a.slug}
                href={`/articles/${a.slug}`}
                className={`${styles.tile} ${tall ? styles.tileTall : ''}`}
              >
                {a.hero?.startsWith('/') ? (
                  <Image
                    src={a.hero}
                    alt=""
                    fill
                    sizes="(max-width: 760px) 50vw, 25vw"
                    className={styles.fill}
                  />
                ) : (
                  <div className={styles.fill} style={{ background: accent }} />
                )}
                <div className={styles.cap}>
                  <span className={styles.capDot} style={{ background: accent }} />
                  <span className={styles.capTitle}>{a.title}</span>
                </div>
              </Link>
            );
          })}
        </div>
      </div>

      <div className={styles.closing}>
        <p className={styles.closingText}>
          A handful of pieces, loosely sorted, some still unfinished. Everything is
          grey until you look at it. Hover to remember which is which.
        </p>
      </div>
    </div>
  );
}

export default async function ArticlesPage({
  searchParams,
}: {
  searchParams: Promise<{ view?: string }>;
}) {
  const { view: rawView } = await searchParams;
  const view = normalizeView(rawView);
  const articles = getAllArticles();

  const collection: JsonLd = {
    '@context': 'https://schema.org',
    '@type': 'CollectionPage',
    name: `${TITLE} — kaspirius`,
    description: DESCRIPTION,
    url: absoluteUrl('/articles'),
    hasPart: articles.map((a) => ({
      '@type': 'Article',
      headline: a.title,
      url: absoluteUrl(`/articles/${a.slug}`),
      datePublished: a.date,
    })),
  };
  const jsonLd = [websiteJsonLd(), collection];

  const tagging: TaggingData = {
    title: TITLE,
    description: DESCRIPTION,
    canonical: absoluteUrl('/articles'),
    headings: articles.map((a) => ({ level: 2 as const, text: a.title })),
    jsonLd,
  };

  return (
    <>
      <Jsonld data={jsonLd} />
      <PageFrame
        view={view}
        basePath="/articles"
        content={<ArticlesIndex />}
        tagging={tagging}
        code={CODE_REFS}
      />
    </>
  );
}
content layerview on GitHub ↗

Reads content/articles/*.mdx, parses frontmatter with gray-matter, and returns sorted Article objects. The same layer feeds the index and the reader.

/* ──────────────────────────────────────────────────────────────────────────
   lib/content.ts — MDX-in-repo content layer.

   Articles live as `content/articles/*.mdx` with YAML frontmatter, read at
   build/request time on the server (Spec: SSR for GEO). No articles ship this
   session — Stage 5 seeds them — but the loader is scaffolded now so later
   stages only add files, not plumbing.
   ────────────────────────────────────────────────────────────────────────── */

import fs from 'node:fs';
import path from 'node:path';

import matter from 'gray-matter';

import { schedule, type ScheduleData } from '@/content/schedule';

const ARTICLES_DIR = path.join(process.cwd(), 'content', 'articles');

export interface ArticleFaq {
  question: string;
  answer: string;
}

/** Frontmatter contract for `content/articles/*.mdx`. */
export interface ArticleFrontmatter {
  title: string;
  slug: string;
  dek: string;
  /** Answer-first TL;DR box (GEO §5). */
  tldr?: string;
  date: string; // ISO published date
  updated?: string; // ISO last-updated date
  author?: string;
  tags?: string[];
  faq?: ArticleFaq[];
  hero?: string;
  /** TechArticle (game/tooling pages) vs Article. */
  techArticle?: boolean;
  /** Draft articles are excluded from listings + static params. */
  draft?: boolean;
}

export interface Article extends ArticleFrontmatter {
  /** Raw MDX body (frontmatter stripped), ready for MDXRemote. */
  content: string;
}

function ensureDir(): boolean {
  return fs.existsSync(ARTICLES_DIR);
}

/** YAML turns unquoted `2026-06-04` into a Date; normalize back to ISO string. */
function toISODate(v: unknown): string | undefined {
  if (v instanceof Date) return v.toISOString().slice(0, 10);
  if (typeof v === 'string') return v;
  return undefined;
}

function readArticleFile(fileName: string): Article | null {
  const full = path.join(ARTICLES_DIR, fileName);
  const raw = fs.readFileSync(full, 'utf8');
  const { data, content } = matter(raw);
  const fm = data as Partial<ArticleFrontmatter>;

  // slug defaults to the filename without extension
  const slug = fm.slug ?? fileName.replace(/\.mdx?$/, '');
  const date = toISODate(fm.date);

  if (!fm.title || !date) {
    // Malformed frontmatter — skip rather than crash the whole listing.
    return null;
  }

  return {
    title: fm.title,
    slug,
    dek: fm.dek ?? '',
    tldr: fm.tldr,
    date,
    updated: toISODate(fm.updated),
    author: fm.author,
    tags: fm.tags ?? [],
    faq: fm.faq ?? [],
    hero: fm.hero,
    techArticle: fm.techArticle ?? false,
    draft: fm.draft ?? false,
    content,
  };
}

/** All non-draft articles, newest first. Returns [] when none exist yet. */
export function getAllArticles(): Article[] {
  if (!ensureDir()) return [];
  return fs
    .readdirSync(ARTICLES_DIR)
    .filter((f) => /\.mdx?$/.test(f))
    .map(readArticleFile)
    .filter((a): a is Article => a !== null && !a.draft)
    .sort((a, b) => (a.date < b.date ? 1 : -1));
}

/** A single article by slug, or null if missing/draft. */
export function getArticle(slug: string): Article | null {
  if (!ensureDir()) return null;
  for (const ext of ['.mdx', '.md']) {
    const candidate = path.join(ARTICLES_DIR, `${slug}${ext}`);
    if (fs.existsSync(candidate)) {
      const article = readArticleFile(`${slug}${ext}`);
      return article && !article.draft ? article : null;
    }
  }
  // Fall back to scanning frontmatter slugs that differ from the filename.
  return getAllArticles().find((a) => a.slug === slug) ?? null;
}

/** Publishing schedule (Stage 4 renders this; data is git-versioned). */
export function getSchedule(): ScheduleData {
  return schedule;
}
↖ kaspirius