Ver en Español
Customizing the configuration in Next.js
Apr 12, 2023
Updated: Jun 25, 2026

Customizing the configuration in Next.js

Next.js makes it remarkably easy to modify the behavior of its execution flow. The most common case, for example, is adding environment variables to your project. In this article we'll show you how to do it and how to extend Next.js through different phases and plugins.

Valid as of Next.js 16: the concept of customizing next.config.js is still fully valid. What changed since 2022 are some API details. In this revision we use next.config.mjs (ESM), fix the handling of webpack, and start from the built in support for .env files.

Adding environment variables

In Next.js, the recommended way to handle environment variables is not to touch next.config.mjs but to use .env files. Next.js loads them automatically as of version 9.4, so you usually don't need to configure anything.

Create a .env.local file at the root of your project and add your variables:

# .env.local
MY_ENV_VAR=HOLA
NEXT_PUBLIC_APP_URL=https://www.sebastian-gomez.com

Important tip: variables you want to read in the browser must be prefixed with NEXT_PUBLIC_. Without that prefix, the variable only exists on the server and arrives as undefined on the client. And never put secrets in NEXT_PUBLIC_ variables, because they end up exposed in the client bundle. Also remember to keep your .env.local out of version control; never commit it to GitHub.

To read the variable in any page or component, just use process.env:

// The variable is prefixed with NEXT_PUBLIC_ so it's available on the client
<a href={process.env.NEXT_PUBLIC_APP_URL}>Go to the app</a>

The next.config.mjs file also has an env key, which still works in Next.js 16, but today it's considered a legacy option: it only inlines values at build time. For most cases, prefer .env.local.

// next.config.mjs — the 'env' key still works, but prefer .env.local today
/** @type {import('next').NextConfig} */
const nextConfig = {
  // These variables are inlined at build time
  env: {
    MY_ENV_VAR: "HOLA",
    OTHER_ENV_VAR: "NO PONGAS SECRETOS AQUI",
  },
};

export default nextConfig;

Exporting the configuration through a function

next.config.mjs can also export a function. This lets us, for example, return a different configuration depending on the build phase Next.js is in:

// Import the 'PHASE_DEVELOPMENT_SERVER' constant from next/constants
import { PHASE_DEVELOPMENT_SERVER } from "next/constants.js";

// Export a function that receives the current phase and the default Next.js config
export default (phase, { defaultConfig }) => {
  // If we're in the development phase, we add an environment variable
  if (phase === PHASE_DEVELOPMENT_SERVER) {
    return {
      ...defaultConfig,
      env: {
        // In development mode we set 'IS_THIS_A_FUNCTION' to 'TRUE'
        IS_THIS_A_FUNCTION: "TRUE",
      },
    };
  }

  // If we're not in development, return the default config
  return defaultConfig;
};

Hooking into Next.js phases

Next.js ships many constants that let us hook into certain phases, such as development, production, or build. A typical case is monitoring the bundle size during compilation.

There's an important detail worth correcting from the original 2022 approach. Bundle analysis happens when the app is built, so the correct phase is PHASE_PRODUCTION_BUILD, not PHASE_PRODUCTION_SERVER (the latter runs while serving, when the bundle already exists). On top of that, in next.config.mjs the webpack key is a function that receives the config and must return it, not an object. Assigning a plain object does nothing.

// Import the 'PHASE_PRODUCTION_BUILD' constant from next/constants
import { PHASE_PRODUCTION_BUILD } from "next/constants.js";
import pkg from "webpack-bundle-analyzer";

const { BundleAnalyzerPlugin } = pkg;

export default (phase, { defaultConfig }) => {
  // Only in the production build phase do we add the plugin
  if (phase === PHASE_PRODUCTION_BUILD) {
    return {
      ...defaultConfig,
      // webpack is a FUNCTION (config) => config, not an object.
      // We mutate the received config and return it.
      webpack: (config) => {
        config.plugins.push(new BundleAnalyzerPlugin());
        return config;
      },
    };
  }

  // In any other phase, return the default config
  return defaultConfig;
};

Note: Next.js 16 uses Turbopack by default, and webpack based bundle analysis only applies when you build with webpack. The idiomatic, maintained approach today is to use the official @next/bundle-analyzer wrapper, which handles everything for you:

// next.config.mjs with the maintained @next/bundle-analyzer wrapper
import bundleAnalyzer from "@next/bundle-analyzer";

const withBundleAnalyzer = bundleAnalyzer({
  // Enabled only when ANALYZE=true
  enabled: process.env.ANALYZE === "true",
});

/** @type {import('next').NextConfig} */
const nextConfig = {
  // Your Next.js configuration
};

export default withBundleAnalyzer(nextConfig);

In the `next/constants` documentation you can see all the different phases Next.js has and all their constants.

Extending the configuration for other file formats

If you're wondering how to make your pages not necessarily JavaScript but also markdown (.md) or extended markdown (.mdx) files, Next.js offers the official @next/mdx package.

There's an important change since 2022. With Turbopack (the default bundler in Next.js 16), remarkPlugins and rehypePlugins must be passed as serializable references, that is, the package name as a string or a [string, options] tuple, instead of imported function instances. That's why we use createMDX like this:

// next.config.mjs
import createMDX from "@next/mdx";

const withMDX = createMDX({
  options: {
    // Plugins as serializable (string) references, not imported instances.
    // Under Turbopack this is required.
    remarkPlugins: [["remark-gfm"], ["remark-emoji"]],
    rehypePlugins: [],
  },
});

/** @type {import('next').NextConfig} */
const nextConfig = {
  // We tell Next.js which extensions should be treated as pages
  pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
};

export default withMDX(nextConfig);

Note: review the list of remark/rehype plugins you actually need. The old remark-images is essentially unmaintained, so here we replace it with remark-gfm, which is the most common one today. Adjust to your case.

In our post on Plugins in Next.js we go deeper into the plugins you can use and how to combine them.

Conclusions

The original Next.js configuration can be modified very easily, and you can extend its functionality as much as you want.

Don't be afraid to do it, because it will let you get much more out of Next.js.

Knowing the different phases and constants in Next.js will help you customize your projects even further.

Exercises to practice

  1. Create a basic Next.js 16 project and add environment variables using a .env.local file, exposing one to the client with the NEXT_PUBLIC_ prefix.
  2. Hook into different project phases using the next/constants constants, for example adding bundle analysis only in the build phase.
  3. Extend your project's configuration to support markdown (.md) and extended markdown (.mdx) files with @next/mdx.

3-point summary

  1. Handle environment variables with the built in .env support; use the NEXT_PUBLIC_ prefix for the ones you need in the browser, and reserve the env key only for build time inlining.
  2. Hook into Next.js phases with the next/constants constants, remembering that webpack is a (config) => config function and that bundle analysis belongs in the build phase (or, even better, use @next/bundle-analyzer).
  3. Extend the configuration to support other formats like markdown with createMDX, passing plugins as serializable references under Turbopack.

That's all, I hope this post is useful to you and that you can apply it to a project you have in mind, or that it simply helped you understand how modifying the configuration of your Next.js projects works.

Leave me a comment if it helped, if you want to add an opinion, or if you have any questions, don't hesitate to write below. And remember, if you liked it, you can also share it using the social links below.

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias