next.config.ts Reference: App Router, Images, Env, and More

Last updated:

next.config.ts is the TypeScript configuration file for Next.js 15 — fully typed, evaluated at build time, and the single place to control images, environment variables, redirects, rewrites, headers, Turbopack, output modes, and experimental flags. This reference covers the options you actually use, with real examples.

1. Minimal next.config.ts

import type { NextConfig } from 'next'

const config: NextConfig = {
  // Options go here
}

export default config

The NextConfig type provides autocomplete and type checking in IDEs. An empty config uses all Next.js defaults — valid for most projects.

2. Images

images: {
  // Allow images from specific domains
  remotePatterns: [
    {
      protocol: 'https',
      hostname: 'images.example.com',
      pathname: '/uploads/**',
    },
    {
      protocol: 'https',
      hostname: '**.cloudinary.com', // wildcard subdomain
    },
  ],

  // Enable AVIF (smaller, more CPU at first request)
  formats: ['image/avif', 'image/webp'],

  // Cache optimized images for 1 week (default: 60s)
  minimumCacheTTL: 604800,

  // Disable image optimization (for static export)
  // unoptimized: true,

  // Custom device widths for srcset
  deviceSizes: [640, 750, 828, 1080, 1200, 1920],
  imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},

3. Environment Variables

// next.config.ts — build-time env injection
env: {
  // Available as process.env.API_BASE_URL everywhere
  API_BASE_URL: process.env.API_BASE_URL ?? 'https://api.example.com',
},
# .env.local — runtime variables (not in git)
DATABASE_URL=postgres://...
NEXT_PUBLIC_ANALYTICS_ID=UA-12345   # NEXT_PUBLIC_ → browser-safe

Variables prefixed with NEXT_PUBLIC_ are inlined at build time and available in client-side code. Never put secrets in NEXT_PUBLIC_ variables.

4. Redirects

async redirects() {
  return [
    // Permanent redirect (301)
    {
      source: '/blog/:slug',
      destination: '/articles/:slug',
      permanent: true,
    },
    // Temporary redirect (302) with regex
    {
      source: '/old-docs/:path((?!new).*)',
      destination: '/docs/:path',
      permanent: false,
    },
    // Redirect with query string preservation
    {
      source: '/search',
      destination: '/find',
      permanent: false,
      has: [{ type: 'query', key: 'q' }], // only when ?q= present
    },
  ]
},

5. Rewrites (Proxy to External API)

async rewrites() {
  return {
    // Evaluated before filesystem routing
    beforeFiles: [],

    // Evaluated after filesystem routing (most common)
    afterFiles: [
      {
        source: '/api/v2/:path*',
        destination: 'https://api.backend.com/v2/:path*',
      },
    ],

    // Evaluated as fallback when no file or page matches
    fallback: [
      {
        source: '/:path*',
        destination: 'https://legacy.example.com/:path*',
      },
    ],
  }
},

6. Security Headers

async headers() {
  return [
    {
      source: '/(.*)',
      headers: [
        { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
        { key: 'X-Content-Type-Options', value: 'nosniff' },
        { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
        {
          key: 'Permissions-Policy',
          value: 'camera=(), microphone=(), geolocation=()',
        },
        {
          key: 'Strict-Transport-Security',
          value: 'max-age=63072000; includeSubDomains; preload',
        },
        {
          key: 'Content-Security-Policy',
          value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src 'self' data: blob:",
        },
      ],
    },
    {
      source: '/api/(.*)',
      headers: [{ key: 'Cache-Control', value: 'no-store' }],
    },
  ]
},

7. Output Mode and Turbopack

// Docker-friendly standalone build
output: 'standalone',
// → .next/standalone/server.js + minimal node_modules

// Static export (no server required)
// output: 'export',
// → out/ directory, deploy to S3/Cloudflare Pages

// Turbopack rules (for dev + build)
turbopack: {
  rules: {
    '*.svg': {
      loaders: ['@svgr/webpack'],
      as: '*.js',
    },
  },
},

// Packages to transpile (monorepo shared packages, ESM-only libs)
transpilePackages: ['@company/ui', 'some-esm-package'],

8. Full Production Example

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: '**.cloudinary.com' },
    ],
    formats: ['image/avif', 'image/webp'],
    minimumCacheTTL: 86400,
  },

  async redirects() {
    return [
      { source: '/home', destination: '/', permanent: true },
    ]
  },

  async rewrites() {
    return {
      afterFiles: [
        { source: '/api/legacy/:path*', destination: 'https://old.api.com/:path*' },
      ],
      fallback: [],
    }
  },

  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
          { key: 'X-Content-Type-Options', value: 'nosniff' },
        ],
      },
    ]
  },

  output: 'standalone',
  transpilePackages: ['@company/ui'],

  experimental: {
    // ppr: true,           // Partial Pre-rendering
    // reactCompiler: true, // React Compiler (experimental)
  },
}

export default config

Frequently Asked Questions

What is next.config.ts and how is it different from next.config.js?

next.config.ts is the TypeScript version of Next.js's configuration file, supported from Next.js 15.0. It has the same capabilities as next.config.js but provides full TypeScript type checking for the configuration object through the NextConfig type from "next". Errors like typos in option names or invalid values are caught at compile time rather than at runtime. To use it, rename next.config.js to next.config.ts and import NextConfig: import type { NextConfig } from "next"; const config: NextConfig = { /* options */ }; export default config. The file runs in Node.js context (not the browser), so you can import Node.js built-ins, read environment variables with process.env, and run async logic to build config values dynamically (using the async function config form). Next.js reads the config file before the build and dev server start, so changes require a restart.

How do I configure image domains and remote patterns in next.config.ts?

In Next.js 13+, the images.domains array is deprecated in favor of images.remotePatterns, which is more precise and supports wildcards. The remotePatterns array takes objects with protocol, hostname, port (optional), and pathname (optional) fields. Example: images: { remotePatterns: [{ protocol: "https", hostname: "images.example.com", pathname: "/uploads/**" }, { protocol: "https", hostname: "**.cloudinary.com" }] }. The double-star wildcard (**) in hostname matches any subdomain including nested subdomains (cdn.images.example.com matches **.example.com). The pathname wildcard similarly matches nested paths. If you do not specify pathname, all paths on that hostname are allowed. images.formats can be set to ["image/avif", "image/webp"] to enable AVIF generation (larger optimization but higher CPU cost on first request). images.minimumCacheTTL (in seconds) controls how long optimized images are cached.

How do I add environment variables in next.config.ts?

Next.js handles environment variables in two ways: (1) .env files — .env, .env.local, .env.development, .env.production. Variables prefixed with NEXT_PUBLIC_ are inlined at build time and available in the browser. Non-prefixed variables are server-only. (2) The env option in next.config.ts — this hardcodes values into the build: env: { MY_CUSTOM_KEY: process.env.MY_CUSTOM_KEY ?? "default" }. These become available as process.env.MY_CUSTOM_KEY in all code. Note that env in next.config.ts is evaluated at build time, not runtime, so it cannot change without a rebuild. For runtime environment variables in App Router (edge or Node.js server components), import from the server and read process.env directly — no config needed. For secrets that should not be in the build artifact, always use .env.local (gitignored) and ensure you are accessing them server-side only.

How do I configure redirects and rewrites in next.config.ts?

redirects and rewrites are async functions that return arrays of route rules. Redirects change the URL the browser sees (301 or 302 HTTP response); rewrites transparently proxy the request to a different URL or path without changing the browser URL. Redirect example: async redirects() { return [{ source: "/old-path/:slug", destination: "/new-path/:slug", permanent: true }] }. Rewrite to an external API: async rewrites() { return [{ source: "/api/external/:path*", destination: "https://api.backend.com/:path*" }] }. Source paths support :param placeholders, wildcards (:path*), and regex patterns. Rewrites run before Next.js routing, so /api/external/users would hit the rewrite before any app/api/external/[...path]/route.ts file. For complex routing, Next.js evaluates rewrites in order: beforeFiles, afterFiles, and fallback arrays let you control precedence relative to file-system routing.

How do I enable Turbopack in next.config.ts?

Turbopack is the Rust-based bundler that replaces webpack in Next.js. For the dev server, use the --turbopack CLI flag (next dev --turbopack) — no config change required. For production builds (next build), Turbopack is still in beta as of Next.js 15. To configure Turbopack-specific options, use the turbopack key (formerly experimental.turbo): turbopack: { rules: { "*.svg": { loaders: ["@svgr/webpack"], as: "*.js" } }, resolveAlias: { "@/utils": "./src/utils" } }. The most common reason to configure turbopack is adding webpack loaders that Turbopack supports in compatibility mode (MDX, SVGR, etc.). Not all webpack loaders are compatible — check the Next.js Turbopack compatibility list. For webpack-specific plugins you cannot migrate, you can still use the webpack config option alongside Turbopack for the production build.

What is the output option in next.config.ts and when should I use "standalone"?

The output option controls what Next.js emits after a build. Three values: (1) Default (unset): the standard .next directory — requires next start or a hosting platform that understands Next.js (Vercel, AWS Amplify, etc.). (2) "standalone": traces and bundles only the files needed to run the app in a minimal Node.js environment, outputting to .next/standalone. This is the right choice for Docker containers — you copy standalone + public + .next/static into the image and run node server.js without npm install. Results in 80-90% smaller Docker images because only the actual dependencies used are included. (3) "export": generates a fully static site (no server) to the out directory. Only works with pages and layouts that use static rendering (no server components with dynamic data, no API routes). output: "export" replaces the old next export command. Use "standalone" for any server-rendered Next.js app deployed in a container; use "export" for static sites deployed to S3, GitHub Pages, or a CDN.

How do I add custom HTTP headers in next.config.ts?

The headers async function returns path-to-headers mappings. Each entry has a source (URL pattern) and a headers array of {key, value} objects: async headers() { return [{ source: "/(.*)", headers: [{ key: "X-Frame-Options", value: "DENY" }, { key: "X-Content-Type-Options", value: "nosniff" }, { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }] }, { source: "/api/(.*)", headers: [{ key: "Cache-Control", value: "no-store" }] }] }. Common security headers to add globally: X-Frame-Options (DENY or SAMEORIGIN), X-Content-Type-Options (nosniff), Strict-Transport-Security (HSTS), Content-Security-Policy, and Permissions-Policy. Note: headers added via next.config.ts headers() are applied at the HTTP response level. For App Router, you can also set cache headers by returning a Response from a route handler or using the fetch cache options. The headers function takes precedence over the same header set in middleware.

What is the transpilePackages option and when do I need it?

transpilePackages is an array of npm package names that Next.js should transpile (run through Babel/SWC) rather than bundling as-is. It is needed when a package ships untranspiled ESM source (TypeScript, JSX, or ES2022+ syntax that the current Node.js version does not support) or when a package uses absolute imports that need alias resolution. Example: transpilePackages: ["@my-company/ui-components", "some-esm-only-lib"]. Before Next.js 13.1, you needed next-transpile-modules for this; it is now built in. You do NOT need transpilePackages for packages that already ship compiled CommonJS or ESM with a .d.ts file — those are used as-is. You also do not need it for packages in the app directory's server component context, which can import any Node.js-compatible module. The most common case is a monorepo where a sibling workspace package is TypeScript source — add it to transpilePackages so the shared components compile correctly in the Next.js build.

Further reading and primary sources