---
type: Integration Guide
title: "Vercel prerendering integration"
description: "Add a prerendering layer to a Vercel-hosted Next.js or SPA without leaving the Vercel runtime — Edge Middleware, Edge Functions, or external prerender service."
resource: "https://prerendering.com/integrations/vercel"
tags: [integration, vercel]
timestamp: 2026-05-11T00:00:00Z
---

# Vercel prerendering integration

Add a prerendering layer to a Vercel-hosted Next.js or SPA without leaving the Vercel runtime — Edge Middleware, Edge Functions, or external prerender service.

Vercel hosts most of the modern Next.js ecosystem. Prerendering integration on Vercel typically uses Edge Middleware to detect crawlers and route them to either an Edge Function rendering layer (for tighter integration) or an external prerendering service (for richer rendering capacity). The right choice depends on your bundle complexity and crawl volume.

## Best for

- Next.js sites already on Vercel
- SPAs and SvelteKit/Nuxt deployed via Vercel
- Teams that want crawler routing inside the platform they already pay for

## Setup

Detect verified bots in Edge Middleware. Route bot requests to a prerender source (Edge Function, an external prerender service, or a self-hosted cluster). Return prerendered HTML with appropriate cache headers.

```js
// Full middleware: https://github.com/ostr-io/vercel-ostr-middleware
export default async function middleware(request) {
  const url = new URL(request.url);

  // Only intercept GET/HEAD for HTML pages
  if (request.method !== "GET" && request.method !== "HEAD") return next();
  if (STATIC_EXT.test(url.pathname)) return next();
  if (url.pathname.includes("/.well-known/")) return next();

  const ua = (request.headers.get("user-agent") || "").toLowerCase();
  const isBot = BOT_RE.test(ua);
  const hasEscapedFragment = url.searchParams.has("_escaped_fragment_");

  if (!isBot && !hasEscapedFragment) return next();

  // Proxy bot request to ostr.io
  const serviceURL = process.env.OSTR_SERVICE_URL || "https://render.ostr.io";
  const auth = process.env.OSTR_AUTH || "";

  // Use canonical origin — ostr.io only allows registered domains
  const canonicalOrigin = process.env.OSTR_SITE_ORIGIN || url.origin;
  let targetUrl = canonicalOrigin + url.pathname;
  if (url.search && url.search.length > 1) {
    const cleanParams = new URLSearchParams(url.searchParams);
    cleanParams.delete("_escaped_fragment_");
    const qs = cleanParams.toString();
    if (qs) targetUrl += "?" + qs;
  }

  const renderUrl = `${serviceURL}/?url=${encodeURIComponent(targetUrl)}&bot=${encodeURIComponent(ua)}`;

  try {
    const headers = new Headers(request.headers);
    headers.delete("Authorization");
    if (auth) headers.set("Authorization", auth);

    const response = await fetch(renderUrl, { headers, redirect: "manual" });

    if (response.ok || response.status === 301 || response.status === 302) {
      return response;
    }

    console.warn(`[ostr-middleware] ostr.io returned ${response.status}, falling back to origin`);
    return next();
  } catch (err) {
    console.warn("[ostr-middleware] ostr.io fetch failed, falling back to origin:", err);
    return next();
  }
}
```

## Fits when

- Site already on Vercel with Edge Middleware available.
- Bot traffic share is meaningful (>5% of fetches by user-agent).
- You want bot routing in the same runtime as the rest of the app.

## Avoid when

- Edge Middleware execution count would push you into a costly tier without offsetting indexation gains.
- You need full Chromium-headless rendering inline (use external service instead).

## Common pitfalls

- **Detecting bots only by user-agent** — Bad actors spoof user-agent. For prerendering compliance, also verify reverse DNS or IP allowlist for Googlebot, Bingbot, and other major crawlers. Mistakenly serving prerendered HTML to spoofers is harmless; mistakenly serving SPA HTML to legitimate bots is the bug.
- **Cache headers that prevent re-fetch** — Edge Middleware can rewrite to a cached prerender, but if response cache headers are too aggressive, content updates do not propagate. Set Cache-Control with a TTL that matches your content freshness SLA (typically 60-3600s).
- **Forgetting to exclude API and static routes** — The matcher must skip /api, /_next, and any other route that should never be prerendered. The example matcher does this; verify yours does too.

## Related

- [Redirect Bot Traffic to Prerendering](/blog/redirect-bot-traffic-to-prerendering.md)
- [Bot Detection and Offloading via Prerendering](/blog/bot-detection-and-offloading-bot-visits.md)
