npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fluojs/platform-nextjs

v2.0.0

Published

Fluo HTTP platform and decorator compiler integration for Next.js App Router and Pages Router.

Readme

@fluojs/platform-nextjs

Connect an existing Fluo backend to Next.js App Router Route Handlers or Pages Router API Routes without replacing Fluo's decorators, modules, factory, or dependency injection.

Table of Contents

Installation

Supported hosts are Next.js 16.x (peer >=16.0.0 <17) on Node.js >=24.11.0 <27 (the Babel 8 compiler toolchain floor), with @fluojs/runtime >=3.0.0 <4. Next.js Edge Runtime is not supported. See the Node.js support contract.

If the application already uses Fluo, add only the adapter:

pnpm add @fluojs/platform-nextjs

For a new Fluo backend inside an existing Next.js application, add the Fluo packages used by application source as direct dependencies too:

pnpm add \
  @fluojs/core \
  @fluojs/http \
  @fluojs/runtime \
  @fluojs/platform-nextjs

No separate Babel package, loader package, or handwritten decorator options are required. The adapter ships the Next-specific decorator loader.

Shared Setup

Enable the packaged decorator transform while preserving the rest of the existing Next configuration:

// next.config.ts
import { withFluoNextBackend } from '@fluojs/platform-nextjs/next-config';

export default withFluoNextBackend({});

Keep the existing decorated Fluo module unchanged:

// src/app.module.ts
import { Module } from '@fluojs/core';
import {
  Controller,
  Get,
  HttpCode,
  Post,
  type RequestContext,
} from '@fluojs/http';

@Controller('/api')
class ApiController {
  @Get('/health')
  health() {
    return { status: 'ok' };
  }

  @Post('/echo')
  @HttpCode(201)
  echo(_input: undefined, context: RequestContext) {
    return { body: context.request.body };
  }
}

@Module({ controllers: [ApiController] })
export class AppModule {}

Create the adapter, pass it to FluoFactory, and start the application exactly like every other Fluo HTTP platform:

// src/backend.ts
import { NextHttpApplicationAdapter } from '@fluojs/platform-nextjs';
import { FluoFactory } from '@fluojs/runtime';

import { AppModule } from './app.module';

export const nextAdapter = NextHttpApplicationAdapter.create();
export const app = await FluoFactory.create(AppModule, {
  adapter: nextAdapter,
});
await app.listen();

App Router

Add one optional catch-all Route Handler:

// app/api/[[...path]]/route.ts
import { createNextAppRouterHandler } from '@fluojs/platform-nextjs/app-router';

export const {
  GET,
  POST,
  PUT,
  PATCH,
  DELETE,
  HEAD,
  OPTIONS,
} = createNextAppRouterHandler(() =>
  import('../../../src/backend').then(({ nextAdapter }) => nextAdapter),
);

Next.js still discovers one filesystem route. Fluo owns decorator metadata, route matching, module bootstrap, dependency injection, request scope, body parsing, errors, and controller dispatch behind that facade. Route Handlers default to the Node.js runtime that Fluo backends require, so no runtime override is needed.

HEAD Routing

Opt in at adapter construction when a GET-only Fluo route should answer Next's automatic HEAD as well as an explicit HEAD export:

import { NextHttpApplicationAdapter } from '@fluojs/platform-nextjs';

export const nextAdapter = NextHttpApplicationAdapter.create({
  headRouting: 'explicit-or-get',
});

Omission preserves existing method matching: a GET-only Fluo route does not implicitly match HEAD. With this option, the shared HTTP matcher selects one eligible explicit @Head route, otherwise @All, otherwise @Get. Method priority precedes path specificity across those groups; normal static/parameter and version rules apply within each group. ALL is a method wildcard, not a path wildcard: Fluo catch-all path grammar remains unsupported.

Selection happens once before module middleware, guards, and the controller. A route miss returns 404. A selected handler's 404 is final and never triggers another dispatch. Application middleware, module middleware, guards, and controller each run at most once, subject to normal short-circuit behavior.

The normalized request and native Request retain HEAD, including inside middleware, guards, version extraction, conditional resolvers, and handlers. The selected descriptor still identifies its declared method, such as GET. Existing conditional-request and byte-range status/header rules therefore remain active. The adapter returns a null body for every opted-in HEAD response, including direct sends, route misses, errors, and unavailable/closed 503s, preserving status, status text, representation headers, and independent cookies. It does not invent a content length when the selected response did not supply one.

Active response streams are cancelled, then the dispatch lifecycle, iterator cleanup, and request-scope disposal are awaited. Stream sources must cooperate with cancellation and settle their iterator return(); cleanup failures retain the dispatcher's observer/logging policy without a GET retry or replacement of already committed metadata. Application-owned resources that never enter a response stream remain application-owned. Prefer a createByteRangeResponse stream factory when HEAD must avoid opening a byte source altogether.

Migration: remove the application wrapper that reconstructs a HEAD request as GET and discards its body. Keep the standard createNextAppRouterHandler(loadAdapter) facade. Export only GET to let Next auto-HEAD call it, or export both GET and HEAD; both use the same policy. Pages Router uses the same adapter option. Update method-based application branches that previously observed GET to recognize the original HEAD. No configuration or behavior change is required for consumers that leave this option unset.

Evidence: head-routing.test.ts, head-routing-public-types.test.ts, and the packaged Next 16 production E2E, which compares auto-HEAD, direct App Router HEAD, and Pages Router HEAD over real HTTP.

Pages Router

Create one optional catch-all API Route. The exported config disables Next's built-in body parser so Fluo receives the original request stream.

// pages/api/[[...path]].ts
import {
  createNextPagesRouterHandler,
  type NextPagesRouterConfig,
} from '@fluojs/platform-nextjs/pages-router';

export default createNextPagesRouterHandler(() =>
  import('../../src/backend').then(({ nextAdapter }) => nextAdapter),
);
export const config = {
  api: {
    bodyParser: false,
  },
} satisfies NextPagesRouterConfig;

The Pages bridge converts the raw IncomingMessage into a Web request, keeps Fluo body parsing and size limits authoritative, and streams the Web response back through ServerResponse.

Input is read on demand rather than queued ahead of the Fluo parser. An oversized upload can receive HTTP 413 before it finishes; only after the response is sent does the bridge discard the remaining input, without taking ownership of the socket. Raw-body capture preserves the original bytes.

Client disconnects propagate to context.request.signal and cancel response stream reads. A disconnect during lazy bootstrap ends that request without dispatching it or cancelling the shared backend startup.

FluoFactory Integration

The Next adapter follows the same platform contract as Fastify and the other Fluo HTTP adapters. Application code creates the adapter, supplies it to FluoFactory.create(), and calls app.listen(). The runtime constructs the application and dispatcher, then attaches the dispatcher through NextHttpApplicationAdapter.listen().

The Next adapter does not create the application and does not open a socket. It dispatches through instance fetch(request) after the dispatcher is attached, while Next.js continues to own the HTTP server. Use the lazy facade above for ordinary App route integration.

Route facades dynamically import src/backend.ts on the first request. Import completion includes its top-level FluoFactory.create() and app.listen(), so handlers cannot observe an unbound adapter. next build compiles the backend chunk without executing that bootstrap.

Pipeline Compatibility

Both router bridges normalize requests into the same Fluo dispatcher used by the other HTTP platforms. The full application pipeline remains active:

  • request Cookie headers and DTO @FromCookie binding
  • response headers and multiple independent Set-Cookie values
  • application and module middleware
  • guards, interceptors, exception filters, and request observers
  • RequestDto binding, global converters, and field-level @Convert
  • request scope, body parsing, multipart handling, and raw body preservation

Fluo does not expose Nest's Pipe abstraction under that name. The equivalent input transformation contract is a global Converter supplied to FluoFactory.create() or a field-level @Convert on a RequestDto.

Next middleware may still rewrite request or response headers before or after the Route Handler. Fluo controllers read the cookie header that reaches their handler through @FromCookie or RequestContext, rather than Next's component-oriented cookies() helper.

App Router and Pages Router are both supported in one hybrid Next application. Because Next emits them as separate server route bundles, enabling both catch-alls with the default per-closure lazy recipe above creates a separate Fluo application per bundle. Sharing one application Promise requires every shared consumer to explicitly use defineNextApplication with the same key and execute within the same JS global (globalThis). See the opt-in sharing contract. Different processes, workers, serverless instances, and JS globals remain isolated. Use one catch-all during migration to avoid per-bundle bootstrap without explicit sharing, or host Fluo separately when deterministic single-instance ownership matters.

Decorator Compiler Wiring

withFluoNextBackend() adds the adapter's packaged loader to the server-side Turbopack *.ts rule for application files, excluding browser and dependency files. The loader applies the same Babel TC39 decorators 2023-11 transform used by @fluojs/vite and returns JavaScript to Turbopack. The removed allowDeclareFields preset option is never passed; declaration-only and decorated fields keep their handling with Babel 8. The packaged Next adapter declares Babel 8 dependencies (@babel/core ^8.0.6, @babel/plugin-proposal-decorators ^8.0.2, and @babel/preset-typescript ^8.0.1).

The packaged compiler integration supports Turbopack only, not webpack. Keep decorated backend declarations in .ts files; the helper does not add a .tsx rule. See the compiler tooling table.

The Vite plugin itself cannot be inserted into Next.js because Vite and Turbopack have different plugin contracts. The shared compiler recipe is the reusable part; the adapter provides the Next-specific loader and config object.

Existing turbopack options, aliases, and rules are preserved. When an application already has a *.ts rule, the Fluo decorator rule is appended. The helper returns a new configuration object and does not mutate its input.

Applications with both a backend and client stores can declare compiler scope and output path preservation in the second argument. This example assumes decorated declarations and DTOs live under src/backend/.

import {
  type FluoNextBackendOptions,
  withFluoNextBackend,
} from '@fluojs/platform-nextjs/next-config';

const backend = {
  include: 'src/backend/**',
  exclude: '**/*.test.ts',
  preserveModulePaths: true,
} satisfies FluoNextBackendOptions;

export default withFluoNextBackend({}, backend);

| Option | Default | Contract | | --- | --- | --- | | include | Omitted | Restrict eligible paths with a Turbopack path glob or RegExp. | | exclude | Omitted | Exclude paths with a Turbopack path glob or RegExp. Both it and include apply. | | preserveModulePaths | false | When true, omit as: '*.js' to retain the original .ts module path. |

Turbopack applies path conditions to project-relative paths under turbopack.root (the Next project by default). The helper does not evaluate patterns itself. include/exclude augment the existing server/application/content conditions; they cannot re-include browser or foreign dependency files. The existing content condition matches @ followed by a word character, so imports and comments can match without decorators. Client-module SSR is not a browser compilation; declare its boundary with include or exclude. The helper does not change the scope of existing user rules.

Omitting the options preserves the existing broad *.ts selection and as: '*.js' output rule. With path preservation, the same Babel JavaScript output is parsed under its original TypeScript filename; no version-specific type: 'ecmascript' option is forced. Include every backend declaration and DTO: decorated files outside the scope may not work without their own compiler configuration. Compiler errors propagate through Next dev/build.

In the real Next 16.3.4 fixture, the legacy helper resolves the client store SSR import as ./store.ts.js, failing dev GET / and the production build. Scope restriction and path preservation are independent opt-ins. See the real Next fixture for reproduction and version-specific verification commands within the supported range.

Lifecycle

The backend module performs ordinary Fluo bootstrap once when the first route request imports it:

const nextAdapter = NextHttpApplicationAdapter.create();
const app = await FluoFactory.create(AppModule, {
  adapter: nextAdapter,
});
await app.listen();

The route handler caches one loader promise, so concurrent first requests share the same backend import and startup. Application code owns the normal Fluo Application. Calling Application.close() runs the standard shutdown lifecycle and closes the adapter. Requests sent afterward receive HTTP 503 problem JSON.

await app.close('manual shutdown');

Next.js owns process startup and shutdown. The package does not register process signal handlers or automatically create a second application after explicit close.

Process-local application accessor

Existing lazy handlers retain one Promise per closure. Choose the root export defineNextApplication({ key, load }) only when RSC, auth callbacks, and Route Handlers must use one application in the same JS process and globalThis. The Node/Next/runtime and decorator compiler prerequisites in Shared Setup remain.

| Field | Contract | | --- | --- | | Input | Fixed application-owned string key, argument-free async load | | Default | Opt-in; existing lazy helpers are unchanged. Definition does not load | | Output/order | The first invocation claims the key and stores a Promise before running load. Calls with that key return the exact same Promise | | Failures | Sync throws and async rejections remain cached. Redefining with another loader retains the first result; there is no automatic retry | | Ownership | The caller owns bootstrap/listen failure cleanup, consumer drain, app.close() or context/container disposal. The accessor registers no signals | | Reload | HMR evaluation does not replace a live graph. After close, the Promise still yields the closed graph. No reset/evict API; restart the host to apply a new bootstrap | | Isolation | Different keys, processes, workers, serverless instances, and JS globals are isolated. This is not a distributed singleton or a guarantee of one connection | | Data | Do not capture request/actor/session data in key or load. Only application resource Promises belong in the global cache; pass request data as method arguments or through request scopes | | Type/identity | All loaders using one key must agree on application type/contract. There is no runtime shape validation or class-name identity inference |

This file reuses src/backend.ts above without each bundle implementing its own global Promise. Route every shared consumer through the accessor; mixing it with direct backend imports can bootstrap outside this cache.

// src/application.ts
import { defineNextApplication } from '@fluojs/platform-nextjs';

export const getApplication = defineNextApplication({
  key: 'my-blog/application/v1',
  load: () => import('./backend'),
});

The route facade obtains its adapter from the same graph.

// app/api/[[...path]]/route.ts
import { createNextAppRouterHandler } from '@fluojs/platform-nextjs/app-router';
import { getApplication } from '../../../src/application';

export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS } =
  createNextAppRouterHandler(() => getApplication().then(({ nextAdapter }) => nextAdapter));

Keep service contracts separate from runtime class imports. Import the following module from the existing AppModule. The owning module registers the provider and alias once each; tokens injected from other modules must be exported.

// src/posts-contract.ts
import { publicToken } from '@fluojs/core';

export interface PostsReader { title(): string }
export const POSTS = publicToken<PostsReader>('my-blog/posts/v1');
// src/posts.module.ts
import { Module } from '@fluojs/core';
import { POSTS, type PostsReader } from './posts-contract';

class PostsService implements PostsReader {
  title() { return 'FluoBlog'; }
}

@Module({
  providers: [PostsService, { provide: POSTS, useExisting: PostsService }],
  exports: [POSTS],
})
export class PostsModule {}

Using this application function from RSC or auth callbacks also removes repeated container.resolve calls and runtime class imports.

// src/posts.ts
import { getApplication } from './application';
import { POSTS } from './posts-contract';

export async function getPosts() {
  return (await getApplication()).app.container.resolve(POSTS); // Promise<PostsReader>
}

Add aliases/exports only for tokens needing a public boundary. Existing class token providers are not replaced, and distinct constructors with identical names are not merged. Symbol.for(...) + useExisting + resolve<PostsReader>(...) remains the valid manual recipe; publicToken only adds inference for that symbol. Every declaration of one namespace must agree on the service contract. Cyclic initialization in which load awaits its own accessor is unsupported.

src/application-accessor.test.ts covers concurrency, failure, module evaluation, and pending/failed/successful close. src/application-public-types.test.ts compiles emitted public declarations. e2e/next.test.mjs exercises separate RSC/auth/route evaluations, overlapping initialization, key/process isolation, request-data separation, and retained close/failure in an actual Next production build.

Options

const nextAdapter = NextHttpApplicationAdapter.create({
  maxBodySize: 1_048_576,
  rawBody: true,
});
const app = await FluoFactory.create(AppModule, {
  adapter: nextAdapter,
  // Ordinary Fluo CreateApplicationOptions stay here.
});
await app.listen();
  • maxBodySize: non-negative maximum request body size in bytes
  • rawBody: preserve parsed request bytes on context.request.rawBody
  • headRouting: optional 'explicit-or-get' selection and bodyless HEAD lifecycle; see HEAD Routing
  • Fluo runtime options remain ordinary FluoFactory.create() options

Runtime Contract

  • App Router Route Handlers and Pages Router API Routes
  • Next.js 16.x (peer >=16.0.0 <17)
  • Node.js >=24.11.0 <27 hosting only; no Edge Runtime
  • @fluojs/runtime peer >=3.0.0 <4
  • withFluoNextBackend() in next.config.ts for Turbopack only
  • request-lazy dynamic backend module import
  • App Router exports: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS
  • Pages Router default NextApiHandler export with bodyParser: false
  • HTTP method availability remains bounded by Next.js routing; the adapter does not add QUERY or other custom method support beyond that host boundary
  • Web-standard Request and Response
  • No raw WebSocket upgrade seam
  • No custom server or process signal ownership
  • Separate lazy applications per catch-all bundle with the default per-closure recipe. Consumers explicitly using defineNextApplication with the same key share only within the same JS global (globalThis), under the opt-in contract. Different processes, workers, serverless instances, and JS globals remain isolated

Use a Fluo Node or Fastify platform adapter when the application requires raw Node.js transport ownership, WebSocket upgrades, or an independently hosted backend.

Public API

| Public import owner | Exports and responsibility | | --- | --- | | @fluojs/platform-nextjs | NextHttpApplicationAdapter.create(options) creates an independent, unbound adapter. NextAdapterOptions, NextAdapterLoader, and InvalidNextAdapterOptionError also belong to the root. | | @fluojs/platform-nextjs | defineNextApplication(options) and NextApplicationOptions<T> own the process-local application Promise for an explicit key. | | @fluojs/platform-nextjs/app-router | createNextAppRouterHandler(loadAdapter), NextAppRouteHandler, NextAppRouterMethodHandlers: lazy Web Request callbacks and the seven method exports required by Next. | | @fluojs/platform-nextjs/pages-router | createNextPagesRouterHandler(loadAdapter), NextPagesRouterConfig: the Node stream bridge and required static bodyParser: false type. | | @fluojs/platform-nextjs/next-config | withFluoNextBackend(config, options?), FluoNextBackendOptions: Turbopack compiler configuration and opt-in scope/path preservation. | | @fluojs/platform-nextjs/decorators-loader | The packaged compiler loader used by the config helper. |

The adapter retains its HttpApplicationAdapter class identity, public constructor and inheritance, and instance fetch(request), listen(dispatcher), close(), and getRealtimeCapability(). Creation does not bind a dispatcher or open a socket; instance execution state is not shared through static fields. Host callbacks and the compiler helper remain distinct capabilities rather than being wrapped in creation classes.

API Migration

This is a major release removing public APIs. There are no permanent compatibility aliases.

| Removed surface | Replacement | | --- | --- | | Root/App/Pages createNextAdapter(options) | Import NextHttpApplicationAdapter from the root and call .create(options). The same option validation and class instance are preserved. | | App/Pages NextHttpApplicationAdapter, NextAdapterOptions, NextAdapterLoader | Move imports to the root. Import App's former InvalidNextAdapterOptionError from the root too. | | Root createNextAppRouterHandler, NextAppRouteHandler, NextAppRouterMethodHandlers | Import from /app-router. | | Root createNextPagesRouterHandler, NextPagesRouterConfig | Import from /pages-router. | | Adapter GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS fields | Destructure the result of createNextAppRouterHandler(loadAdapter) into route module exports. Lazily import the backend as in the App Router recipe above. |

Do not delete Next route module exports such as GET/POST/HEAD. The adapter's low-level instance dispatch remains fetch(request), but do not replace lazy bootstrap with an eager backend import or direct bound handler export in a route. Do not convert Pages into a Web Request callback. Preserve bodyParser: false, demand-driven input, drain waits for ServerResponse.write() backpressure, disconnect abort, and remaining-upload drain after the response completes. Next retains socket ownership.

Each facade's closure cache retains only that facade's loader Promise. The defineNextApplication keyed cache retains the first Promise and failure across consumers in the same JS global. Do not attribute one cache's guarantees to the other. Neither automatically retries failure or reopens a closed application.

The consumer audit includes package tests/public declarations, the real Next fixture, Docs runtime-adapters/http-platform pages, and Book volume 3 chapter 15 creation/import paths. Current CLI generators and examples/ have no consumers of this Next API. The executable example is the package fixture. Historical changelogs retain the API that existed at the time. New source and distributed JavaScript/.d.ts do not retain the removed exports.

src/public-api.test.ts, src/app-router.test.ts, src/head-routing-public-types.test.ts, and real Next E2E verify creation, host methods, cache distinctions, and import ownership. Internal createLazyNextAdapterResolver is the actual closure-cache implementation shared by the App/Pages bridges, not a public package export. Documentation code-fence import ownership is checked by pnpm verify:platform-consistency-governance.

Development Verification

From the repository root:

pnpm --filter '@fluojs/platform-nextjs...' build
pnpm --filter @fluojs/platform-nextjs typecheck
pnpm --filter @fluojs/platform-nextjs test
pnpm --filter @fluojs/platform-nextjs test:e2e

The unit suite includes shared Web portability checks and native Pages transport regressions. The E2E suite stages package distribution files, builds a real Next.js application through dev/build/start, and verifies both routers and client store SSR over HTTP without source aliases. See the fixture guide.

Bounded Body Parsing

Set NextAdapterOptions.bodyParser at adapter construction. It accepts the HTTP-owned BodyParser: 'default' (also the omitted default), 'text', or a synchronous/asynchronous callback. Application-wide text mode is NextHttpApplicationAdapter.create({ bodyParser: 'text' }). For path-specific policy, delegate unselected paths to the unchanged parser instead of reconstructing Requests:

import { NextHttpApplicationAdapter } from '@fluojs/platform-nextjs';

export const nextAdapter = NextHttpApplicationAdapter.create({
  headRouting: 'explicit-or-get',
  maxBodySize: 1_048_576,
  rawBody: true,
  bodyParser(text, context) {
    if (context.path === '/api/posts/draft') return text;
    return context.parseDefault();
  },
});

Pass this adapter to the existing FluoFactory.create() and keep the ordinary App/Pages facade. Paths are original URL pathnames before Fluo route matching; there are no matched params or route metadata at this stage. Multipart keeps its existing parser/limits and does not invoke the callback. The HTTP contract and capability matrix own defaults, empty/absent bodies, raw bytes, errors, and abort cooperation.

Authentication boundary: bounded reading and custom callbacks precede Fluo middleware, guards, and filters. To return 401 before malformed-JSON 400, receive text, authenticate explicitly in middleware/a guard, then parse JSON in the handler (or a later application stage), translating syntax errors to BadRequestException. Oversized input still returns 413 before authentication. Text mode does not authenticate, reorder the pipeline, or make invalid JSON null.

Migration: remove the wrapper that changes Content-Type to text/plain and constructs a replacement Request. Keep original headers and pass the original Request to the standard facade. The adapter already consumes the original body without cloning; a proxied Request.clone failure in that removed wrapper is not a Next adapter defect. rawBody: true alone is not a JSON parsing bypass. Pages Router still exports Next api: { bodyParser: false } so Fluo owns the raw stream; an upstream parsed host body cannot recover original bytes.

Evidence: src/body-parser.test.ts, the cold public declaration tests in src/head-routing-public-types.test.ts, and e2e/fixture/backend.ts plus e2e/next.test.mjs. The production fixture uses this public callback and no request/header rewriting for App and Pages routes, including default delegation, UTF-8 limits, raw bytes, empty/malformed/non-JSON inputs, and auth precedence.