Skip to content
Tabrej Alam
Writing

· 2 min read

How I built tabrejalam.com: a static Next.js site driven by content files

A fully static Next.js export with typed content files, honest status labels, per-page SEO, JSON-LD, an image sitemap and no backend to maintain.

  • Full-stack development
  • Next.js
  • SEO

This site is the permanent home for my work: experience, projects, apps, ventures and writing. I wanted it to be fast, easy to maintain for years, and honest about what's finished and what isn't. Here's how it's built.

Requirements

  • No backend, database, CMS or auth. Nothing to patch, scale or pay for.
  • Content-driven. Adding a project or app should mean editing a data file, not a component.
  • Honest labels. Professional work, independent projects, concepts and planned apps must be clearly separated.
  • Strong SEO without third-party services.

Stack

  • Next.js App Router with output: "export" — every route is pre-rendered to static HTML at build time.
  • TypeScript for content and components.
  • Tailwind CSS v4 with a small set of design tokens: near-black background, charcoal surfaces, fine borders and a single electric-blue accent.
  • Motion for restrained scroll reveals, with reduced-motion respected.
  • MDX for articles like this one.
  • Vercel for hosting.

Content as typed data

Everything lives in src/content/ as TypeScript: profile.ts, experience.ts, projects.ts, apps.ts, skills.ts, achievements.ts, ventures.ts and writing.ts. Types such as Project and MobileApp define exactly what a case study or app page needs.

A few details I like:

  • Drafts. A project marked draft: true is excluded from pages, static params and the sitemap — so incomplete information never ships.
  • Disclosure flags. Professional figures can be stored with a publish flag and only render when they're cleared for public use.
  • Status labels. Projects and apps carry statuses like "Prototype", "Hackathon build" or "Planned", and the Play Store button only appears for apps that are actually published.

Dynamic routes, statically generated

Case studies, apps and articles use dynamic segments with generateStaticParams() and dynamicParams = false. At build time, Next.js renders one HTML file per item; anything else is a 404. App privacy and support pages are generated the same way, so every app automatically gets /apps/[slug]/privacy/ and /apps/[slug]/support/.

Articles are MDX files loaded with a dynamic import inside a single /writing/[slug] route, so every post shares the same layout, metadata, structured data and reading-time calculation.

SEO without a server

  • Per-page metadata from a single buildMetadata() helper: title, description, canonical URL, Open Graph and Twitter cards.
  • JSON-LD for Person, WebSite, BreadcrumbList, BlogPosting, SoftwareSourceCode and SoftwareApplication.
  • Sitemap and robots generated from the same content files, including lastmod and image entries.
  • An Open Graph image rendered at build time with next/og into a static og.png.
  • An RSS feed at /feed.xml.

Performance and accessibility

  • Self-hosted Geist fonts — no third-party font requests.
  • Server Components everywhere except the header and small interactive pieces.
  • A skip link, visible focus styles, semantic landmarks and one h1 per page.
  • Every route was checked at mobile and desktop widths for horizontal overflow and broken links.

Privacy and security

Analytics use Vercel Web Analytics, which doesn't use cookies. Security headers — Content-Security-Policy, HSTS, X-Frame-Options and others — are set in vercel.json, which also redirects www to the canonical domain.

Maintaining it

The workflow is: edit a content file, run npm run check (types, lint and production build), push. That's the whole point — a site I can keep updating for years without rewriting it.