· 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: trueis excluded from pages, static params and the sitemap — so incomplete information never ships. - Disclosure flags. Professional figures can be stored with a
publishflag 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,SoftwareSourceCodeandSoftwareApplication. - Sitemap and robots generated from the same content files, including
lastmodand image entries. - An Open Graph image rendered at build time with
next/oginto a staticog.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
h1per 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.