Today Platform Web — Dev Docs
Toolchain

Next.js 16

Four 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/tangible, apps/webview-bridge-docs, and 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 apps/web/scripts/dev-server.mts.

All four apps use Turbopack for next build. apps/web and apps/tangible also keep explicit build:webpack scripts as rollback paths while their custom webpack configuration remains supported.

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.ts → proxy.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