How to Fix: Middleware: Cannot export config after declaration in export list format

5 min read

Encountering the "Cannot export config after declaration in export list format" error in your Next.js middleware is a common, albeit frustrating, roadblock. This isn’t a bug in your standard JavaScript module syntax but rather a strict requirement from Next.js’s build system for efficient static analysis and optimization of your middleware functions. Let’s dive into why this happens and how to resolve it.

Understanding the Root Cause

Next.js treats the middleware.js (or .ts) file as a special entry point, applying specific static analysis during the build process to optimize how your middleware runs. The config object, particularly its matcher property, is crucial for telling Next.js which paths your middleware should apply to. This allows Next.js to efficiently tree-shake and optimize the middleware bundle, ensuring only relevant code is executed for specific requests.

The core of the problem lies in the way Next.js expects this config object to be declared and exported. Unlike standard ES modules where you might declare a variable and then export it later in an export list (e.g., const config = { /* ... */ }; export { config };), Next.js demands a direct, top-level named export:

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

This explicit syntax enables Next.js to statically analyze the config object at build time without executing any JavaScript. If you use a separate declaration and then an export list, Next.js’s static analyzer cannot reliably determine the config‘s value without potentially executing code, which it avoids for safety and performance reasons during its static pass. The error "Cannot export config after declaration in export list format" is Next.js’s way of enforcing this critical static analysis requirement.

Step-by-Step Solution

Solving this issue is straightforward once you understand Next.js’s specific requirement. You simply need to refactor your config declaration and export to a single, top-level statement.

Step 1: Locate Your Middleware File

Find your middleware.js or middleware.ts file. This is typically located at the root of your project or inside the src/ directory.

Step 2: Identify the Problematic Export

Look for lines similar to this pattern, where config is declared separately and then exported:

// Incorrect Pattern
import { NextResponse } from 'next/server';

export function middleware(request) {
  // ... middleware logic ...
}

const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

export { config }; // <-- This is the problematic line

Step 3: Refactor to a Direct Export

Change the separate declaration and export to a single export const statement:

// Correct Pattern
import { NextResponse } from 'next/server';

export function middleware(request) {
  // ... middleware logic ...
  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

By using export const config = { /* ... */ };, you satisfy Next.js’s static analysis requirements, allowing it to correctly identify and use your middleware configuration for optimal performance and behavior.

Step 4: Restart Your Development Server

After making this change, ensure you restart your development server (e.g., npm run dev or yarn dev) to pick up the updated middleware configuration.

Common Edge Cases

While the fix is usually simple, here are a few other scenarios or pitfalls related to middleware config that might cause similar or related issues:

  • Non-Plain Object for config: Next.js expects the exported config to be a literal, plain JavaScript object. If you try to derive it from a function call, a complex computation, or a non-serializable object, you might encounter similar errors or unexpected behavior. For example:

    // Incorrect: Dynamic config
    const getMatcher = () => ['/dynamic-path'];
    export const config = {
      matcher: getMatcher(), // <-- This won't work
    };
    

    The matcher array must be defined statically.

  • Typos in Property Names: Ensure that your property name is exactly matcher. A typo like matchers or matcherConfig will be ignored, leading to your middleware running on all paths (if no other matcher is found) or not at all.

  • Incorrect File Location: The middleware.js (or .ts) file must be at the root of your project or inside the src/ directory. Placing it elsewhere will prevent Next.js from discovering it.

  • Multiple config Exports: You can only have one export const config in your middleware file. Multiple declarations will lead to errors.

  • TypeScript Considerations: If you’re using TypeScript, you can explicitly type your config for better autocompletion and error checking:

    import { type NextMiddleware, type MiddlewareConfig } from 'next/server';
    
    export const config: MiddlewareConfig = {
      matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
    };
    
    export const middleware: NextMiddleware = (request) => {
      // ...
    };
    

    This doesn’t directly solve the export format issue but is good practice.

FAQ

Q1: Why does Next.js enforce this specific config export syntax?

Next.js enforces export const config = { /* ... */ }; for its static analysis capabilities. This allows the framework to read and optimize your middleware’s configuration (especially the matcher) at build time without needing to execute any JavaScript. This optimization is crucial for performance, enabling Next.js to efficiently bundle and deploy your middleware.

Q2: Can I dynamically generate the matcher array for the middleware config?

No, the matcher array within the config object must be statically defined. Next.js’s build-time static analysis requires that the value of matcher be known at compile time. You cannot use variables, function calls, or other dynamic logic to construct the array. If you need dynamic routing logic, it must be handled inside the middleware function itself, after the initial static matching.

Q3: What is the matcher property in middleware config and why is it important?

The matcher property is an array of strings that defines the paths for which your middleware function should be invoked. It uses standard path-to-regexp syntax, allowing for powerful pattern matching. It’s incredibly important because it acts as the first line of defense for your middleware, preventing unnecessary execution for paths that don’t need to be processed. This significantly improves performance by reducing overhead and resource consumption.

Q4: Does this export restriction apply to other Next.js special files like layout.js or page.js?

No, this specific export restriction for config is unique to the middleware.js (or .ts) file. Other Next.js special files, like layout.js, page.js, or API routes, adhere to standard ES module export patterns for their components, data fetching functions, or API handlers. The middleware’s strict config format is a specific optimization applied due to its unique role in request handling.

Leave a Reply

Your email address will not be published. Required fields are marked *