Next.js adapter
Mount the gateway as Next middleware (@rebilder/gateway/next) — plus an optional always-markdown route for previews and agent permalinks.
How it mounts
The adapter imports nothing from next — Next middleware/proxy handlers and app-router route handlers speak web-standard Request/Response, so standard types are the whole contract. This repo’s apps use the Next 16 proxy.ts convention; the identical code works in a Next ≤15 middleware.ts.
npm install @rebilder/gateway # pnpm add / yarn addThe 2-line integration
// lib/gateway-config.ts — wire the gateway to your source of truth
import type { GatewayConfig } from '@rebilder/gateway'
import { getProduct, getPolicies, getCollection } from './catalog' // your code
export const gatewayConfig: GatewayConfig = {
storeId: 'store_123',
sources: {
product: (url) => getProduct(url.pathname), // null when not a PDP
policies: (url) => getPolicies(url.pathname),
catalog: (url) => getCollection(url.pathname),
},
onEvent: (event) => { /* queue to your analytics sink; fire-and-forget */ },
}// proxy.ts (Next 16) — middleware.ts on Next ≤15 is identical
import { NextResponse } from 'next/server'
import { createGatewayProxy } from '@rebilder/gateway/next'
import { gatewayConfig } from './lib/gateway-config'
const gateway = createGatewayProxy(gatewayConfig)
export default async function proxy(req: Request) {
return (await gateway(req)) ?? NextResponse.next()
}
export const config = { matcher: ['/products/:path*', '/policies/:path*', '/collections/:path*'] }A Response short-circuits with markdown; null continues to your HTML pipeline unchanged — humans, crawlers, protocol routes, URLs no source matched, and sources that threw all pass through. A thrown source never breaks your site: errors are contained, the event still fires, the request falls through to HTML.
First live integration (dogfood): trymumm.com wires exactly this in its proxy.ts — gateway first, ?? updateSession(request) fallthrough — proving the compose-with-existing-middleware pattern in production.
Matcher scope
Scope config.matcher to the paths your sources can answer (/products/:path*, /policies/:path*, /collections/:path* in the example). Requests outside the matcher never reach the gateway at all — no classification, no event. If you widen sources later, widen the matcher in the same change.
Optional: a dedicated markdown route
A stable always-markdown URL — an agent permalink, or the "what agents see" preview. It renders markdown for any requester and returns 404 JSON ({ "error": "not_found" }) when no source matches:
// app/md/[[...path]]/route.ts
import { createGatewayRouteHandler } from '@rebilder/gateway/next'
import { gatewayConfig } from '../../../lib/gateway-config'
export const GET = createGatewayRouteHandler(gatewayConfig, { stripPrefix: '/md' })stripPrefix removes the route prefix (whole path segments only) before consulting your sources, so /md/products/x resolves against sources keyed by canonical paths (/products/x). This route is its own URL, so it doesn’t conflict with the cloaking guardrail — the guardrail is about serving different substance on the *same* URL.
llms.txt route
The same subpath export ships createLlmsTxtRouteHandler for serving a deterministic llms.txt from your gateway config — usage, options, and the honest effectiveness caveat live on the llms.txt page.