This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
Diff Shuttle — a free, browser-based text and code comparison tool. Built with Next.js 16 + React 19, deployed to Cloudflare Pages via static export.
| Task | Command |
|---|---|
| Dev server | npm run dev |
| Build | npm run build |
| Lint | npm run lint (runs eslint) |
npm run build=next build && node scripts/postbuild.mjs.next buildtype-checks and statically exports toout/; the postbuild step then rewrites that output (see Build pipeline below). Always run the fullnpm run build—next buildalone produces a brokenout/.- There is no test suite and no
deployscript. Deploy withnpx wrangler pages deploy out(or via the Cloudflare dashboard) after building.
scripts/postbuild.mjs runs after next build and is load-bearing — the static export does not match the site's own metadata without it:
- Promotes
out/en/*→out/and deletesout/en. WithlocalePrefix: "as-needed", Next emits English underout/en/, but every canonical/hreflang/sitemap URL declares English at the web root. This makes the output match. - Patches
<html lang>fromentozh-CNacrossout/zh/**/*.html. - Generates
out/sw.js— a service worker with a precache list derived from the actual emitted routes (cache-first for/_next/static/, network-first for navigations, stale-while-revalidate for everything else). Cache name is versioned per build.
app/— Next.js App Routerapp/layout.tsx— Root layout (<html lang="en">, metadata, JSON-LD,ThemeProvider,ServiceWorkerRegister, sonnerToaster)app/manifest.ts,app/icon-192.png/route.tsx,app/icon-512.png/route.tsx— PWA manifest + dynamically generated iconsapp/sitemap.ts,app/robots.ts,app/opengraph-image.tsx,app/not-found.tsx— SEO/PWA route handlersapp/[locale]/— i18n routeslayout.tsx— Locale layout (NextIntlClientProvider, Header, Footer)page.tsx— Homepage with DiffEditor toolabout/— About page with FAQ schemaprivacy/— Privacy policyterms/— Terms of servicetools/— Layer 4 SEO landing pages (English only):code-diff,csv-diff,image-diff,json-diff,text-diff
components/— React componentsdiff-editor.tsx— Main diff input componentdiff-result.tsx— Diff result displaydiff-controls.tsx— Diff mode/view controlsdiff-stats.tsx— Statistics displayhome-content.tsx/layout-shell.tsx— homepage body and shared page chrometheme-sync.tsx—ThemeProvider+useTheme()(custom; nonext-themes)palette-picker.tsx— combined light/dark/system + accent-palette popoverservice-worker-register.tsx— registers/sw.json the clientAboutFaq.tsx+AboutFaqData.tsx— FAQ data and renderingui/— shadcn components (built on@base-ui/reactprimitives, not Radix)
lib/— Shared utilitiesdiff-engine.ts— Core diff algorithm (npm 'diff' library)constants.ts— Constantsutils.ts—cn()utility
i18n/— next-intl configurationrouting.ts— Locale routing (en,zh,as-needed)request.ts— Server-side translationsnavigation.ts— Link, useRouter, etc.
messages/— Translation files (en.json, zh.json)
- URL-based with next-intl (
localePrefix: "as-needed") - Default locale:
en(served at/) - Chinese:
zh(served at/zh) - Layer 4 pages: English only (
/tools/*) - Server components:
getTranslations({ locale, namespace }) - Client components:
useTranslations("namespace") - Always add both
enandzhentries when adding UI text
- Uses npm
difflibrary for comparison algorithms - Supports: text (line-by-line), word, character, JSON, CSV
- JSON mode: auto-formats before comparison
- CSV mode: line-by-line comparison
- Export: text format with statistics
- Tailwind CSS v4 + shadcn/ui, with shadcn components wrapping
@base-ui/reactprimitives - Geist + Geist Mono fonts
- Theme is custom (
components/theme-sync.tsx), notnext-themes:- Mode: System/Light/Dark, persisted in
localStorage["theme"], applied by toggling.darkon<html>.systemstores nothing and followsprefers-color-scheme. - Palette: 6 accent palettes (
default,sakura,mint,ocean,sunset,graphite), persisted inlocalStorage["palette"], applied via<html data-palette>(default uses no attribute). Add a new palette in thePALETTESarray and define its[data-palette=…]tokens inapp/globals.css.
- Mode: System/Light/Dark, persisted in
- Toast: sonner (top-center, richColors, 3s)
- Icons: lucide-react
- Path alias:
@/maps to project root
- Static export (
output: "export"in next.config.ts),trailingSlash: true,images.unoptimized: true wrangler.tomlserves./outas static assets only (no worker)- No middleware (not supported with static export)
- PWA: service worker generated at postbuild (
out/sw.js), registered byservice-worker-register.tsx
- Layer 1: Homepage (500+ words + tool UI)
- Layer 3: About (FAQPage + HowTo schemas), Privacy, Terms
- Layer 4: 6 tool landing pages (800-1500 words each)
- All pages have proper metadata, canonical URLs, and hreflang
- See
SEO_OVERVIEW.mdfor complete asset map
AGPL-3.0-only