An airline booking demo, from flight search to a stored trip, that demonstrates Instant Navigations in Next.js 16.4.
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. Search results and flight offers use'use cache: remote'so serverless instances share one cache, and the active traveler is read with'use cache: private'. - Partial Prefetching prefetches one shared App Shell per route. The link to the next booking step uses
prefetch={true}, which also resolves its cached offer. prefetch()andnavigation()are awaited in the components that render trips, holds, and flight lists, which moves those reads to a per-link prefetch or to the navigation while keeping them cached.- Server Functions hold seats, confirm, and cancel trips on the server, and invalidate only the tags they change with
updateTag. - React Compiler memoizes components and hooks automatically, so the code needs no manual
useMemooruseCallback. - View Transitions animate content as it streams in, while the header, tab bar, and demo toolbar stay in place.
- Async React keeps the UI interactive during server work with
Suspense,useOptimistic, anduseTransition.
Each read is cached differently and arrives at a different stage of a navigation.
| Read | How it is cached | When it arrives |
|---|---|---|
| Airports and destinations | 'use cache' |
In the static shell |
| Offer, seats, prices | 'use cache: remote', tagged |
With the prefetch |
| Your trips and your hold | 'use cache' per session, after unstable_prefetch() |
With a per-link prefetch |
| Flight list, who holds what | 'use cache', after unstable_navigation() |
On the navigation |
| Seats left | Uncached | On every request |
Holding a seat updates only the seat map's tag, so the flight list is not recomputed.
Waypoint runs on Postgres and on a Next.js 16.4 canary, which unstable_prefetch() and unstable_navigation() need. Set DATABASE_URL in .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.
Run locally without Postgres
Drop this prompt into your agent to swap the datasource for SQLite:
Set up Waypoint to run locally on SQLite instead of Postgres. Keep both database adapter stacks installed so the production Postgres setup remains available. Swap
provider = "postgresql"toprovider = "sqlite"inprisma/schema.prisma. Replace@prisma/adapter-pgwith@prisma/adapter-better-sqlite3inlib/db.tsandprisma/seed.ts, usingnew PrismaBetterSqlite3({ url })whereurlisprocess.env.DATABASE_URLwith thefile:prefix stripped, and skipnormalizeDatabaseUrlfor file URLs inprisma.config.ts. WriteDATABASE_URL=file:./prisma/dev.dbto.env.local, then runpnpm run prisma.pushandpnpm run prisma.seed.
The schema is otherwise identical, so the rest of the app behaves the same as production.
The end-to-end tests use @next/playwright with the instant() API to assert that the App Shell renders immediately and that navigations stay instant, and they run in CI.
pnpm test:e2eStatic checks:
pnpm lint
pnpm typecheck- Next.js 16.4 canary: App Router, Cache Components, Partial Prefetching,
prefetch()andnavigation(), Server Functions - React 19 with React Compiler: Suspense, View Transitions,
useOptimistic - TypeScript and Tailwind CSS v4
- Prisma 7 on PostgreSQL
- Ariakit for accessible dialogs and popovers
- Playwright with
@next/playwrightfor end-to-end tests