> ## Documentation Index > Fetch the complete documentation index at: https://bunny.net/docs/llms.txt > Use this file to discover all available pages before exploring further. # Serve Next.js from Bunny CDN with Bunny Optimizer > Put a bunny.net pull zone in front of a self-hosted Next.js app, and resize next/image requests at the edge with Bunny Optimizer. A custom `next/image` loader sends every image request to [Bunny Optimizer](https://bunny.net/docs/optimizer/dynamic-images/overview.md), which resizes and converts it at the edge. Your origin never runs `/_next/image`, and the pull zone caches each width. The [example app](https://github.com/BunnyWay/examples/tree/main/cdn/optimizer-nextjs-standalone) covers both setups on this page. > [!NOTE] > > Next.js apps deployed to Magic Containers with a CDN endpoint already come with a pull zone in front of the whole app. Turn on Optimizer for that zone with the steps in [Add Optimizer](https://bunny.net/docs/magic-containers/guides/nextjs.md#add-optimizer). For a static Next.js site on Bunny Storage, see [Resize images with Bunny Optimizer](https://bunny.net/docs/storage/static-site-hosting/nextjs.md#resize-images-with-bunny-optimizer). ## Set up Optimizer and the loader **1. Create a pull zone** [Create a pull zone](https://bunny.net/docs/cdn/quickstart.md) with your app's origin URL, then turn on [URL Query String](https://bunny.net/docs/cdn/vary-cache.md#url-query-string) vary. Without it, `?width=1080` comes back as the cached `?width=640` image. **2. Enable Optimizer** Enable [Bunny Optimizer](https://bunny.net/docs/optimizer/quickstart.md) on the pull zone, then turn on the Dynamic Image API and automatic WebP under **Optimizer → Settings**. Next.js already minifies CSS and JavaScript, so turn Optimizer's minification off. **3. Add the loader** The loader turns each `next/image` width into an Optimizer URL. It needs `"use client"` because the App Router sends the loader to the browser. ```typescript lib/bunny-loader.ts theme={null} "use client"; import type { ImageLoaderProps } from "next/image"; const cdn = process.env.NEXT_PUBLIC_CDN_URL; export default function bunnyLoader({ src, width, quality }: ImageLoaderProps) { const url = new URL(src, cdn || "http://localhost"); const params = url.searchParams; if (!params.has("width")) params.set("width", String(width)); if (!params.has("quality")) params.set("quality", String(quality ?? 75)); const absolute = cdn || /^https?:\/\//.test(src); return absolute ? url.href : url.pathname + url.search; } ``` Any parameters already on `src` stay, so [Dynamic Image API](https://bunny.net/docs/optimizer/dynamic-images/overview.md) options combine with the responsive widths: ```tsx theme={null} Photo ``` Optimizer defaults to quality 85. The loader sends Next.js's default of 75 unless a component sets its own. **4. Configure Next.js** ```typescript next.config.ts theme={null} import type { NextConfig } from "next"; // Assets and images only: your pull zone URL const cdn = process.env.NEXT_PUBLIC_CDN_URL; // Whole site: your public hostnames, comma separated const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(","); const nextConfig: NextConfig = { output: "standalone", assetPrefix: cdn, experimental: { serverActions: { allowedOrigins } }, images: { loader: "custom", loaderFile: "./lib/bunny-loader.ts", qualities: [50, 75], }, async headers() { return [ { source: "/images/:path*", headers: [{ key: "Cache-Control", value: "public, max-age=86400" }], }, ]; }, }; export default nextConfig; ``` Next.js 16 only accepts quality 75 unless you list other values in `images.qualities`. The `headers()` rule caches `public/images` for a day, since Next.js serves public files with `max-age=0`. ## Choose what the pull zone serves > [tab] Assets and images only > > Visitors load HTML from your host, and Next.js loads scripts, styles, fonts, and images from the pull zone. > > **1. Respect origin cache headers** > > Set the pull zone to respect the origin's `Cache-Control` header. Next.js sends year-long immutable headers on `/_next/static/*`. > > **2. Add CORS headers for fonts** > > Add `woff2` and `woff` under **Headers**. `next/font` serves fonts from `/_next/static/media` on the pull zone hostname. > > **3. Block every other path** > > Add an [edge rule](https://bunny.net/docs/cdn/edge-rules.md) with the **Block Request** action that triggers when the request URL matches none of `*/_next/static/*` and `*/images/*`. Without it, the pull zone serves your pages on its own hostname, and Next.js sends `s-maxage=31536000` on static pages, so the edge can cache a year-old copy of your HTML. > > **4. Build with the pull zone URL** > > Next.js inlines `NEXT_PUBLIC_` variables at build time: > > ```bash theme={null} > NEXT_PUBLIC_CDN_URL=https://myapp.b-cdn.net npm run build > ``` > > Deploy the standalone build, for example to [Magic Containers](https://bunny.net/docs/magic-containers/guides/nextjs.md). The `