> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeroclick.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Serve agent markdown in front of a SPA

> A client-rendered SPA serves agents an empty shell. Deploy an edge function in front of it that detects agent traffic and serves Agentify markdown, failing open to your app.

Agents fetch your pages the way `curl` does: one GET, no JavaScript. A client-rendered single-page app answers that request with an empty shell — a root `<div>` and a script tag — so every agent that lands on your site reads nothing. [Agentify](/agentify/overview) solves half of this on its own: when it converts a JavaScript-rendered page, it renders the page in a headless browser first, so the conversion carries your real content.

The other half is serving that markdown from *your* domain. A SPA on a CDN — Cloudflare Pages, Vercel, Netlify, S3 behind CloudFront — has no server to mount the [SDK middleware](/agentify/sdk-middleware) in. The fix is an edge function in front of the SPA that implements the same contract: detect agent traffic, serve Agentify markdown, and fall through to the app on everything else, including any Agentify failure. The snippets below are that function for the three common hosts.

<Info>
  You need an API key with the `agentify:convert` scope, minted in the [dashboard](https://dashboard.zeroclick.io) under **Settings → API keys** (the **Agentify** group's **Convert** toggle). Add it to your host as the secret `ZEROCLICK_AGENTIFY_KEY`. The edge function needs no signing secret — it verifies nothing, so the standalone SDK helpers are all it uses.
</Info>

## Deploy the edge function

Each snippet uses `withAgentify` from [`@zeroclickai/sellers`](https://www.npmjs.com/package/@zeroclickai/sellers) — the SDK is web-native, so it runs unchanged in edge runtimes — with a one-line client that supplies the key. `withAgentify` handles the whole contract: GET-only, [detection](/agentify/overview#how-detection-works) over `Accept` and `User-Agent`, forwarded cache headers plus `Vary`, and fall-through to your app on any failure.

<CodeGroup>
  ```ts Cloudflare Pages theme={null}
  // functions/_middleware.ts — runs in front of every Pages asset.
  // Set ZEROCLICK_AGENTIFY_KEY as a production secret on the project.
  import { fetchAgentifyMarkdown, withAgentify } from "@zeroclickai/sellers";

  interface Env {
    ZEROCLICK_AGENTIFY_KEY: string;
  }

  export const onRequest: PagesFunction<Env> = (context) =>
    withAgentify(
      {
        fetchAgentifyMarkdown: (url) =>
          fetchAgentifyMarkdown(
            // Multi-seller organizations add: seller: "sel_…"
            { url },
            { apiKey: context.env.ZEROCLICK_AGENTIFY_KEY },
          ),
      },
      () => context.next(),
    )(context.request);
  ```

  ```ts Vercel theme={null}
  // middleware.ts at the project root. Set ZEROCLICK_AGENTIFY_KEY in the
  // project's environment variables. In a Next.js app, return
  // NextResponse.next() instead of @vercel/edge's next().
  import { next } from "@vercel/edge";
  import { fetchAgentifyMarkdown, withAgentify } from "@zeroclickai/sellers";

  export default function middleware(request: Request): Promise<Response> {
    return withAgentify(
      {
        fetchAgentifyMarkdown: (url) =>
          fetchAgentifyMarkdown(
            // Multi-seller organizations add: seller: "sel_…"
            { url },
            { apiKey: process.env.ZEROCLICK_AGENTIFY_KEY! },
          ),
      },
      () => next(),
    )(request);
  }
  ```

  ```ts Netlify theme={null}
  // netlify/edge-functions/agentify.ts. Set ZEROCLICK_AGENTIFY_KEY as an
  // environment variable on the site.
  import type { Context } from "@netlify/edge-functions";
  import { fetchAgentifyMarkdown, withAgentify } from "@zeroclickai/sellers";

  export default (request: Request, context: Context): Promise<Response> =>
    withAgentify(
      {
        fetchAgentifyMarkdown: (url) =>
          fetchAgentifyMarkdown(
            // Multi-seller organizations add: seller: "sel_…"
            { url },
            { apiKey: Netlify.env.get("ZEROCLICK_AGENTIFY_KEY")! },
          ),
      },
      () => context.next(),
    )(request);

  export const config = { path: "/*" };
  ```
</CodeGroup>

On another host, port the same shape to its edge runtime, or put a small server in front of the CDN and mount the [SDK middleware](/agentify/sdk-middleware) there — the contract is identical. Detection only fires for markdown-preferring or AI-agent requests, so browsers, asset fetches, and search crawlers never touch the Agentify path; if your host supports path matchers, you can still scope the function to page routes and skip asset paths entirely.

## Verify

```sh theme={null}
# An agent's request gets markdown with your storefront inlined:
curl -s -H "Accept: text/markdown, text/html, */*" https://www.acme.com/ | head -20

# A browser Accept header gets the SPA shell, unchanged:
curl -s -H "Accept: text/html,application/xhtml+xml" https://www.acme.com/ | head -5
```

The first request of each page takes a few seconds while the conversion runs; repeats serve from [cache](/agentify/overview#caching). Remove the key from the environment and the markdown request serves the shell instead — that is the fail-open path working, not a deployment error.

## If conversion answers `422 content_too_thin`

The error means the page yielded too little readable content even after rendering: the content appears only after user interaction, sits behind a bot wall, or never reaches the DOM at all. Two fixes, in order of preference:

* **Prerender or server-render your marketing pages.** Most SPA frameworks can emit static HTML for public pages at build time. That helps every raw-text reader, not just Agentify.
* **Serve the content in the initial HTML.** Agentify converts what a fetch (or a headless render of it) can see; content that only exists after a click cannot be converted.

Your billed API needs none of this — agents transact with it through your [storefront](/website/enable-agent-traffic), which is machine-readable by construction.

## Next steps

<Columns cols={2}>
  <Card title="SDK middleware" icon="package" href="/agentify/sdk-middleware">
    The same contract as drop-in middleware, when you do have a server.
  </Card>

  <Card title="REST quickstart" icon="rocket" href="/agentify/quickstart-rest">
    The underlying endpoint: request, response anatomy, and errors.
  </Card>
</Columns>
