All guides

Allow embedding in Next.js

Add a Content-Security-Policy header with frame-ancestors in the headers() function of next.config.ts. If you already build a CSP in proxy.ts or middleware, add frame-ancestors to that policy instead.

4 min read · Updated

Find the current header

curl -sI https://your-site.com/ | grep -iE "x-frame-options|content-security-policy"

A framing header comes from your Next.js config, your code, or your host. Search the project:

grep -Rni "x-frame-options\|frame-ancestors" next.config.* proxy.* middleware.* vercel.json src/ app/ 2>/dev/null

Remove any X-Frame-Options entry you find. Security templates often copy X-Frame-Options: DENY into next.config.

Allow one partner site with headers()

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: "/:path*",
        headers: [
          {
            key: "Content-Security-Policy",
            value: "frame-ancestors 'self' https://partner.example",
          },
        ],
      },
    ];
  },
};

export default nextConfig;

If you already set a full CSP here, add frame-ancestors to that same string. Do not add a second Content-Security-Policy entry.

Allow framing on one path only

When two entries match a path and set the same key, the last entry wins. So put the general rule first and the embed rule last:

async headers() {
  return [
    {
      source: "/:path*",
      headers: [{ key: "Content-Security-Policy", value: "frame-ancestors 'self'" }],
    },
    {
      source: "/embed/:path*",
      headers: [{ key: "Content-Security-Policy", value: "frame-ancestors https://partner.example" }],
    },
  ];
},

If you build the CSP in proxy.ts

Next.js 16 renamed middleware.ts to proxy.ts. If you make a nonce-based CSP there, add frame-ancestors to that policy:

// proxy.ts
import { NextResponse, type NextRequest } from "next/server";

export function proxy(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
  const csp = [
    "default-src 'self'",
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
    "frame-ancestors 'self' https://partner.example",
  ].join("; ");

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-nonce", nonce);
  requestHeaders.set("Content-Security-Policy", csp);

  const response = NextResponse.next({ request: { headers: requestHeaders } });
  response.headers.set("Content-Security-Policy", csp);
  return response;
}

On Next.js 15 or older, the file is middleware.ts and the function is middleware. Keep the CSP in proxy.ts or in next.config, not in both. Otherwise the page can get two policies.

Check the result

Run next build && next start, or deploy. Then run the curl command again. You want one CSP line with your frame-ancestors and no X-Frame-Options.

Common mistakes

  • output: "export". A static export does not support headers(). Set the header on the host that serves the files, for example in Netlify or Vercel config.
  • CSP in a <meta> tag or in metadata. frame-ancestors only works as a response header.
  • The host adds X-Frame-Options. A CDN or reverse proxy in front of Next.js can add it. Check the live URL, not only localhost.
  • ALLOW-FROM. Browsers ignore it.

Check the live page

Paste the deployed URL into the iframe tester. It shows if the page can load in a frame and explains why.

To check many pages at once, use the bulk check. It shows X-Frame-Options and frame-ancestors for up to 100 URLs.

Sources

Check your page now

Know before you embed.

Free, no sign-up, nothing stored. Paste a URL and get a verdict, a live preview, and the fix.