Today Platform Web — Dev Docs
Toolchain

Next.js 16

Three Next.js apps on Next 16 with Turbopack and the renamed `proxy.ts` middleware file. What changed from Next 15 and what to watch for.

apps/web, apps/admin, and apps/webview-bridge-docs / apps/dev-docs all run Next.js 16 with Turbopack. The catalog pins next: ^16.2.6.

Turbopack

Turbopack is now the default next dev bundler in Next 16. The repo opts in explicitly in each docs site:

// apps/dev-docs/package.json
"scripts": {
  "dev": "next dev --turbopack -p 4070"
}

For apps/web, next dev already defaults to Turbopack in 16; the dev script hides this behind the per-worktree port scanner in scripts/dev-server.mjs.

For next build, apps/web still uses webpack explicitly (next build --webpack) because some of the production-only bundle optimizations differ enough that we want stability there. The docs sites use Turbopack for next build because they ship pure MDX with no JS heavy lifting.

You will see this warning during builds:

⚠ Warning: Next.js inferred your workspace root, but it may not be correct.
 Detected additional lockfiles: ...

This appears when you build from a .worktrees/ checkout — Next sees two pnpm-workspace.yaml files (the main checkout and the worktree). It is harmless; the main checkout's lockfile wins for resolution but the worktree's sources are what get built. Setting turbopack.root in next.config.ts would silence it; we accept the warning to keep the config minimal.

middleware.tsproxy.ts

Next 16 renamed the conventional middleware filename. The exported function is the same shape; only the filename and the exported name changed:

// apps/web/src/proxy.ts (Next 16)
import { NextRequest, NextResponse } from 'next/server'

export default function proxy(request: NextRequest) {
  if (!request.cookies.has('better-auth.session_token') && request.nextUrl.pathname === '/') {
    return NextResponse.rewrite(new URL('/waitlist', request.nextUrl))
  }
  return NextResponse.next()
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}

The runtime is Node-only under the new name — Edge runtime is no longer the implicit default. If you need Edge for a specific proxy, opt in explicitly in the matcher config (we haven't needed to so far).

apps/web/src/proxy.ts is intentionally tiny — just one rewrite for unauthenticated visitors hitting /. Anything beyond that lives in route handlers or layouts.

File-system convention summary

Next 15 fileNext 16 equivalent
middleware.ts / middleware.jsproxy.ts / proxy.js
export default function middlewareexport default function proxy
(everything else under app/)unchanged

The repo's docs sites also use proxy.ts (see apps/dev-docs/proxy.ts) for Fumadocs' markdown-content-negotiation rewrites.

Configuration

apps/dev-docs/next.config.ts is the smallest possible Next 16 config in this repo:

import { createMDX } from 'fumadocs-mdx/next'

const withMDX = createMDX()

const config = {
  reactStrictMode: true,
  typescript: {
    tsconfigPath: './tsconfig.app.json',
  },
}

export default withMDX(config)

typescript.tsconfigPath is set to ./tsconfig.app.json because the project splits its tsconfig — see tsconfig references. Next needs to know which one drives the build.

React 19

The catalog pins react: ^19.2.6 and react-dom: ^19.2.6. Server components, server actions, and the new use() hook are all in scope. Notable runtime behaviors that bit us during the upgrade:

  • useEffect dependency arrays are now type-checked more strictly under react-hooks/exhaustive-deps. Several places had to add deps explicitly.
  • Suspense boundaries during streaming SSR are sensitive to throwing components above the boundary. A loose fetch error will crash the page rather than fall through to a boundary if there isn't one strictly above.

When in doubt, run pnpm dev and watch the browser console — React 19's warnings are explicit and actionable.

Migration debt

A few low-priority cleanups remain from the Next 15 → 16 jump:

  • Some metadata exports still use string for template instead of Metadata['title']. Works; minor type churn.
  • A few Route typed-route imports are still pinned to the experimental v15 export shape. Compiles fine; should follow up when we touch those routes.

Both tracked in TODO.md.

On this page