Documentation menu

Quickstart

Install the gateway in a Next.js app, wire it to a product catalog, and verify with two curl commands. Target: under 30 minutes, no conversation required.

Before you start

This walkthrough uses Next.js (Next 16 proxy.ts; the identical code works in a Next ≤15 middleware.ts). You need your product data reachable in-process or via a fast cache: the gateway sits on the edge hot path (p95 < 50ms compute budget), so source resolvers must not do slow origin round-trips.

On a different stack? The config in steps 2–3 is identical everywhere; only the mounting changes. See the Shopify, Node (Express/Fastify), and Cloudflare/edge adapter pages.

Installing with an agent? Start here

Copy this into Claude Code, Cursor, Copilot, or whatever you build with. It covers every step below, and it front-loads the two things that are easy to get wrong and hard to notice later: passing the fallthrough, and checking every URL you wired rather than the first one that worked.

give this to your agent
Install @rebilder/gateway in this repository.

WHAT IT DOES
It is middleware. When an AI agent requests one of our URLs with
`Accept: text/markdown`, it answers with a clean markdown rendering of that
page's facts. Every other request — humans, Googlebot, anything that did not ask
for markdown — falls through to the existing pipeline untouched. Same URL, same
substance, different format.

STEPS
1. Read the README of the installed package before writing anything. Do not
   work from memory of this prompt; the API is in
   node_modules/@rebilder/gateway/README.md.
2. Add the dependency with the package manager this repo already uses.
3. Create a gateway config exporting a `GatewayConfig`. Its `sources` resolvers
   map a URL to our own data. Wire the adapter for this repo's framework
   (`/next`, `/node` for Express and Fastify, `/edge` for Workers, `/shopify`).
4. Pass the existing fallthrough into the adapter rather than calling it
   yourself. That is what puts `Vary: Accept` on the HTML response. Without it a
   shared cache can serve markdown to a human or stale HTML to an agent, and it
   will not show up until it is in production behind a CDN.
5. Add a `match` router to `sources` if you can: one pure, synchronous function
   from URL to source kind. It keeps the hot path to a single lookup and it is
   what lets the adapter advertise the markdown alternate.

RULES — these are not style preferences
- Never invent a substantive value. Prices, stock, shipping costs, return
  windows, policy text and dates are read from our source of truth and passed
  through unchanged. If you cannot find where a value lives, stop and ask me.
  Do not approximate, do not use a placeholder that looks real, and do not
  write an example price into a resolver.
- A resolver returns `null` when the URL is not that kind of page. `null` means
  "fall through to HTML", which is always a safe answer.
- Do not change what the HTML pages render. This is additive.
- Do not add a build step, a new service, or a runtime dependency.

WHEN YOU ARE DONE
Tell me which URL patterns you wired and which you deliberately skipped, then
run this against one real URL of EACH kind you wired — not just the first one:

  npx rebilder diff <url>     # shows the agent view and the browser view
  npx rebilder check <url>    # grades the page, names what is missing

An incomplete source map is the most common install defect and it is invisible
until an agent hits the page you missed. Both commands run locally, upload
nothing, and need no account.

If you cannot answer "which URLs now serve markdown", the install is not done.

Rather do it by hand? The rest of this page is the same install, written out.

1. Install

terminal
npm install @rebilder/gateway        # pnpm add / yarn add

One package, zero external runtime dependencies. The gateway re-exports every type you need (ProductSource, PolicySource, CatalogItemSource, DetectionResult, RebilderEventV0, …), so nothing else gets installed.

2. Wire your source of truth

The gateway renders from your data and never invents content. Give it lookup functions from a URL to your catalog. Here is a complete, realistic example (in a real store these functions read your database, CMS, or platform cache; the shape is what matters):

lib/catalog.ts
// lib/catalog.ts — your source of truth, typed with the gateway's re-exported types
import type { ProductSource, PolicySource } from '@rebilder/gateway'

const PRODUCTS: Record<string, ProductSource> = {
  '/products/alpine-trail-pack-28l': {
    url: 'https://store.example.com/products/alpine-trail-pack-28l',
    title: 'Alpine Trail Pack 28L',
    brand: 'Basecamp Supply Co',
    description:
      'A 28-liter technical daypack: suspended-mesh back panel, magnetic sternum buckle, 3L hydration sleeve, lifetime repair guarantee.',
    price: { amount: 14800, currency: 'USD' }, // minor units — integer cents, never floats
    availability: 'in_stock', // 'in_stock' | 'out_of_stock' | 'preorder' | 'backorder'
    variants: [
      {
        id: 'BSC-ATP28-JUN',
        title: 'Juniper Green',
        price: { amount: 14800, currency: 'USD' },
        availability: 'in_stock',
        options: { Color: 'Juniper Green' },
      },
      {
        id: 'BSC-ATP28-EMB',
        title: 'Ember Orange',
        price: { amount: 14800, currency: 'USD' },
        availability: 'backorder',
        options: { Color: 'Ember Orange' },
      },
    ],
    shipping: {
      summary: 'Free US shipping on orders over $75; standard shipping $6.95.',
      freeThreshold: { amount: 7500, currency: 'USD' },
      regions: ['US', 'CA'],
      etaDays: [3, 6], // [min, max] delivery estimate in days
    },
    returns: {
      summary: '60-day returns. Items must be unused with tags attached.',
      windowDays: 60,
      url: 'https://store.example.com/policies/returns',
    },
    images: [
      { url: 'https://store.example.com/cdn/atp28-front.jpg', alt: 'Alpine Trail Pack 28L, front' },
    ],
    attributes: { Capacity: '28 L', Weight: '1.12 kg (2.47 lb)' },
  },
}

const POLICIES: PolicySource[] = [
  {
    title: 'Shipping policy',
    url: 'https://store.example.com/policies/shipping',
    body: 'Orders placed before 12pm PT ship the same business day. Free US shipping over $75.',
  },
  {
    title: 'Returns policy',
    url: 'https://store.example.com/policies/returns',
    body: '60-day returns. Items must be unused with tags attached. Refunds to the original payment method.',
  },
]

export function getProduct(pathname: string): ProductSource | null {
  return PRODUCTS[pathname] ?? null // null = "not a product page" — the gateway moves on
}

export function getPolicies(pathname: string): PolicySource[] | null {
  return pathname.startsWith('/policies') ? POLICIES : null
}

Returning null means "this URL isn’t mine": the gateway tries the next source, and if nothing matches, the request passes through to your HTML. Money amounts are integer minor units (cents): 14800 renders as $148.00, and a non-integer amount throws rather than silently rounding. The full field reference is on the Sources page.

3. Create the gateway config

lib/gateway-config.ts
// 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 */ },
}
  • storeId is stamped on every emitted event, so use the id from your Console once you have one.
  • onEvent is optional and fire-and-forget: the gateway never awaits it, and a broken sink can neither block nor break a response. Wire it to the HTTP sink later; see Events.
  • maxBytes (not shown) caps the markdown size; default 5120 bytes.

4. Add the proxy

proxy.ts
// 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'

// Pass your fallthrough. The proxy then returns a Response for every request,
// and the HTML half of each negotiated URL gets `Vary: Accept` — see below for
// why that matters more than it looks.
const gateway = createGatewayProxy(gatewayConfig, () => NextResponse.next())

export default gateway

export const config = { matcher: ['/products/:path*', '/policies/:path*', '/collections/:path*'] }

That’s the whole integration: a Response short-circuits with markdown, null continues to your HTML pipeline unchanged. Scope the matcher to the paths your sources can answer. If you already have a middleware.ts (auth, session refresh), compose with the same ?? pattern: gateway first, your middleware as the fallthrough. The first live integration (trymumm.com) does exactly that.

5. Verify with curl

Prove both audiences get the right thing from the same URL. These are the two commands from the captured demo; against your own deployment, point them at one of your product URLs instead:

terminal
# 1. What an agent gets — clean markdown (note x-rebilder-path: markdown)
curl -si -H 'Accept: text/markdown' https://trymumm.com/trust/refunds

# 2. What a browser (or Googlebot) gets — the canonical HTML, unchanged
curl -si https://trymumm.com/trust/refunds

What you should see

  • Command 1: 200 with content-type: text/markdown; charset=utf-8, vary: Accept, and x-rebilder-path: markdown, followed by front-loaded markdown.
  • Command 2: your normal HTML, byte-for-byte what you served before installing the gateway.

The markdown body leads with the buying facts (title, price, availability, shipping, returns) in the first screenful. From the captured demo response:

response body (excerpt)
# [Alpine Trail Pack 28L](https://basecamp-supply.example/products/alpine-trail-pack-28l)

- **Brand:** Basecamp Supply Co
- **Price:** $148.00
- **Availability:** In stock
- **Shipping:** Free US shipping on orders over $75; standard shipping $6.95.

6. Check every URL you wired, not the first one

Step 5 proves the install works. It does not prove your source map is complete, and an incomplete map is the most common install defect, because it is invisible until an agent hits the page you missed.

terminal
npx rebilder diff https://your-store.example/products/some-product
npx rebilder check https://your-store.example/products/some-product
  • diff fetches the URL twice (once as a browser, once as an agent) and shows you both views side by side.
  • check grades the page and names what is still missing.
  • Run them against one URL of each kind you wired (a product, a policy, a collection, your homepage), not just the one you were working on.
  • Both run on your machine, upload nothing, and need no account.

Next: wire events so visits show up in your Console, and serve an llms.txt while you’re at it.