Files
supabase/apps/studio/compat/next/server.ts
Alaister Young 6946ec2b2d build(studio): Next-compat shims (stack 2/6, from #46424) (#47110)
**Stack 2/6** of the TanStack Start migration (#46424). Stacked on
**#47107** (S1) — review that first; this PR's diff is just the compat
shims.

> [!NOTE]
> Purely additive. Next never imports these files — under TanStack
they're wired in via Vite aliases (`next/*` → `@/compat/next/*`). No
routes consume them yet (that begins in stack 3).

## What's in this PR
`apps/studio/compat/next/*` — drop-in shims so the existing pages-router
code runs unchanged under TanStack Start:
- `link`, `router`, `navigation`, `head`, `image`, `legacy/image`,
`script`, `dynamic`, `server`, `_router-events` — React/runtime shims
over `@tanstack/react-router`.
- `api.ts` — `toWebHandler`, which adapts a pages-router API handler
`(req, res)` into a TanStack server-route Web `fetch` handler.

## Verification
On top of S1: `studio` typecheck ✓, lint (0 errors) ✓. Next build is
unaffected (nothing imports these under tsc).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added broad Next.js compatibility support for routing, links, dynamic
imports, images, scripts, head metadata, navigation hooks, server
responses, and API handlers.
* Improved handling of redirects, pathname/search params, base paths,
and event callbacks for smoother app behavior.

* **Tests**
* Added coverage for URL resolution and dynamic route interpolation to
verify Next-style routing behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2026-06-25 16:52:34 +08:00

78 lines
3.4 KiB
TypeScript

// Vite-side compat for `next/server`. Studio's middleware (proxy.ts) and
// a couple of App Router routes import from here. Most of this surface
// isn't actually exercised at runtime under TanStack/Vite — middleware
// doesn't run, and the App Router routes only call NextResponse.json —
// but the imports need to resolve to real values so the bundle compiles
// and consumers don't crash if they're invoked.
type NextInit = ResponseInit & {
// Next attaches a `request` bag on `next()` to allow middleware to
// mutate request headers before forwarding. Accepted-and-ignored here.
request?: { headers?: HeadersInit }
}
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308])
function buildResponse(body: BodyInit | null, init?: NextInit): Response {
// Strip the Next-only `request` field before handing off to the
// standard Response constructor — it'd be retained as a non-standard
// option and trip strict implementations.
if (init && 'request' in init) {
const { request: _request, ...rest } = init
return new Response(body, rest)
}
return new Response(body, init)
}
export const NextResponse = {
json: (data: unknown, init?: NextInit) => {
// Response.json sets Content-Type for us; merge any caller headers.
return Response.json(data, init && 'request' in init ? { ...init, request: undefined } : init)
},
// Middleware uses `NextResponse.next()` to signal "continue without
// rewriting". Under TanStack we have no middleware runtime, so the
// returned Response is effectively a placeholder that mirrors the
// shape Next produces (empty 200).
next: (init?: NextInit): Response => buildResponse(null, init),
redirect: (url: string | URL, init?: number | NextInit): Response => {
const status = typeof init === 'number' ? init : (init?.status ?? 307)
if (!REDIRECT_STATUSES.has(status)) {
throw new RangeError(
`[next/server compat] NextResponse.redirect: invalid status ${status}; expected one of ${[...REDIRECT_STATUSES].join(', ')}`
)
}
const headers = new Headers(typeof init === 'object' ? init?.headers : undefined)
headers.set('Location', String(url))
return new Response(null, { status, headers })
},
// Used in middleware to rewrite an incoming request to a different
// path without changing the visible URL. Encoded by setting the
// `x-middleware-rewrite` header (matches Next's runtime contract).
rewrite: (destination: string | URL, init?: NextInit): Response => {
const headers = new Headers(init?.headers)
headers.set('x-middleware-rewrite', String(destination))
return buildResponse(null, { ...init, headers })
},
error: (): Response => new Response(null, { status: 500 }),
}
// NextRequest extends Request with `nextUrl`, `cookies`, `geo`, and `ip`.
// None of our workspace source reads those fields at runtime — most
// imports are type-only. Aliasing the runtime value to the global
// Request constructor keeps value imports working (e.g. `import {
// NextRequest } from 'next/server'` followed by `(req: NextRequest) => …`
// type annotations under verbatimModuleSyntax / isolatedModules).
export const NextRequest = Request
export type NextRequest = Request
// Stub the type-only exports Next ships so consumers importing them
// don't need to special-case the shim.
export type NextMiddleware = (request: Request) => Response | Promise<Response> | undefined | void
export type MiddlewareConfig = {
matcher?: string | string[]
}