A Gmail-style mail client that demonstrates Instant Navigations in Next.js 16.5, including the navigation() API.
The architecture follows the Next.js App Architecture skill and the Component Architecture for React Server Components blog post.
- Cache Components cache each query with
'use cache', name the data withcacheTag, and set its lifetime withcacheLife. The signed-in account is read with'use cache: private'and passed into each cached query. - Partial Prefetching prefetches one shared App Shell per route. Mailbox links, pager links, and the thread rows in view use
prefetch={true}, which also resolves each thread's header. Database-backed label routes prefetch on hover. navigation()is awaited in the message components before they query, which keeps message bodies out of every prefetch while their cached results still serve later visits.- Server Functions star, archive, mark read, reply, compose, and undo a send on the server, and invalidate only the tags they change with
updateTag. New text is screened through the Vercel AI Gateway. - React Compiler memoizes components and hooks automatically, so the code needs no manual
useMemooruseCallback. - View Transitions animate content as it streams in from Suspense.
- Async React keeps the UI interactive during server work with
Suspense,useOptimistic,useTransition, anduse.
Each read is cached and arrives at a different stage of a navigation.
| Read | How it is cached | When it arrives |
|---|---|---|
| Mailbox links and unread counts | 'use cache' per account |
In the App Shell |
| One page of a mailbox | 'use cache' per account, mailbox, and page |
With a mailbox or pager link's prefetch |
| Subject, sender, labels, and toolbar | 'use cache' per thread and account |
With a row's prefetch |
| The latest message and the reply form | 'use cache' per thread, after navigation() |
On the navigation |
| Earlier messages in the thread | 'use cache' per thread, after navigation() |
On the navigation, below the latest one |
Opening a thread shows its header first. The latest message and the reply form appear together, and earlier messages stream in below them.
Stamp runs on SQLite locally and Postgres in production, on a Next.js 16.5 canary. Copy .env.example to .env.local, then:
pnpm install
pnpm run prisma.push
pnpm run prisma.seed
pnpm run devOpen http://localhost:3000 in your browser. You can browse the data with pnpm run prisma.studio, or wipe and re-seed the database with pnpm run prisma.reset. Restart next dev after re-seeding, since cached reads outlive the rows they came from.
The seed gives one fictional person a work mailbox and a personal mailbox. Switch between them from the sidebar, and write from one to the other. Use the toolbar in the top right to turn Prefetch off and compare, or turn Delays on to watch the header arrive before the newest message, and the newest message before the history. Set VERCEL_AI_GATEWAY_TOKEN to screen replies and new messages for spam and profanity.
Deploying with Postgres
The driver follows DATABASE_URL. A file: URL uses SQLite through @prisma/adapter-better-sqlite3, and a postgresql:// URL uses Postgres through @prisma/adapter-pg. prisma.config.ts swaps the schema provider to match, so prisma generate, db push and db seed work against either. Point DATABASE_URL at your Postgres database and run the same three commands to seed production data.
The end-to-end tests use @next/playwright with the instant() API to assert that a row's thread header is available before the click and that the message bodies only render after it. They run in CI against Postgres; locally they run against a separate SQLite database.
pnpm test:e2eStatic checks:
pnpm lint
pnpm typecheck- Next.js 16.5 canary: App Router, Cache Components, Partial Prefetching,
navigation(), Server Functions - React 19.3 with React Compiler: Suspense, View Transitions,
useOptimistic - TypeScript and Tailwind CSS v4
- Prisma 7 on SQLite locally and PostgreSQL in production
- Ariakit for the account menu, the recipient combobox, and the mobile navigation drawer
- Playwright with
@next/playwrightfor end-to-end tests