This is the repository for my personal website, evanwu.dev: portfolio, blog, photography, and a few utility pages. It is built as a bilingual (English / Chinese) Next.js app and deployed on Vercel.
- Localized routing — English is the default locale with unprefixed URLs (
/about). Chinese lives under/zh/*. Locale is driven by next-intl; routing config is insrc/i18n/routing.tsand mirrorslanguine.json(source localeen, targetzh). - Blog / articles — MDX files per locale in
src/content/{locale}/, compiled on the server with next-mdx-remote-client, remark-gfm, and Shiki for code blocks. Reading time is computed insrc/lib/mdx.ts. - Dynamic OG images —
src/app/api/og/route.tsxgenerates Open Graph images for sharing. - RSS — Per-locale feeds under
src/app/[locale]/rss/route.ts. - Email subscriptions — Optional: server action in
src/actions/subscription.tsstores signups in Neon PostgreSQL whenDATABASE_URLis set. - Analytics — Vercel Analytics and Speed Insights (
@vercel/analytics,@vercel/speed-insights).
| Area | Choice |
|---|---|
| Framework | Next.js 16 (App Router), React 19, React Compiler (reactCompiler: true in next.config.ts) |
| Language | TypeScript |
| Styling | Tailwind CSS v4 (@tailwindcss/postcss) |
| i18n | next-intl — useTranslations(), locale-aware Link / router from src/i18n/navigation.ts |
| Content | MDX + YAML frontmatter; UI copy in messages/en.json and messages/zh.json |
| MDX | Custom components in src/components/mdx.tsx (headings, links, code via mdx-code) |
| UI | Radix UI (Dialog, Tooltip), lucide-react, motion |
| Theming | next-themes |
| Validation | Zod (e.g. subscription form) |
| Quality | Biome — bun run lint runs biome format --write |
| Package manager | Bun |
src/
app/
[locale]/ # Localized pages: home, about, articles, projects, photography, uses, …
api/og/ # OG image route
robots.ts, sitemap.ts
actions/ # Server actions (e.g. subscription)
components/ # React components (RSC by default; `'use client'` only when needed)
content/
en/, zh/ # *.mdx articles per locale
i18n/ # routing.ts, navigation.ts, request.ts
lib/ # mdx.ts, constants (resume, projects, nav), fonts, rss, …
styles/ # Global CSS
messages/
en.json, zh.json # next-intl message catalogs
Shared data (navigation, resume snippets, projects, social links) is centralized in src/lib/constants.ts.
Each src/content/{locale}/*.mdx file can include YAML frontmatter:
---
title: 'Article title'
description: 'Short description for SEO and listings'
publishedAt: 'YYYY-MM-DD'
image: '/optional-og-or-card-image.png'
---Slugs are derived from the filename (e.g. react-server-component.mdx → /articles/react-server-component).
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
No | Neon connection string for the email subscription feature. Without it, other site features still work; only the subscription action needs the DB. |
Vercel sets NODE_ENV; local dev defaults to http://localhost:3000 for RSS/OG base URLs where applicable.
bun install
bun dev # Next dev (Turbopack)
bun run build # Production build
bun run start # Run production server locally
bun run typecheck # tsc --noEmit
bun run lint # Biome: format with --write- Prefer named exports; default exports only where the framework requires them.
- kebab-case directories, PascalCase component files.
- Path aliases:
@/*→./src/*. - Formatting: Biome — 2-space indent, single quotes, no semicolons, ~80-char width (see
biome.json).
The site is intended to run on Vercel. Connect the repo, set DATABASE_URL if you use subscriptions, and deploy. sitemap.ts and robots.ts live under src/app/ for crawlers.
