Skip to main content
doc0blade47/next.jsdocumentation home
Sign in

Webpack Loaders

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

Next.js employs a specialized suite of webpack loaders to synthesize routing structures, handle SWC Compiler transformations, manage React Server Component boundaries, and optimize production bundles across diverse runtime environmentsWebpack Configuration. These loaders intercept raw source files and virtual entrypoints, injecting runtime adapters, transpiling modern syntax, pruning unused barrel exports, and resolving complex file-system hierarchies into executable JavaScript modules. By bridging filesystem conventions with bundler compilation rules, the loader architecture coordinates client browser execution, Node.js server rendering, and edge runtime compilation targets.

Sources: packages/next/src/build/webpack-config.ts:605-774, packages/next/src/build/webpack/loaders/next-swc-loader.ts:62-80

App router component tree synthesis

Execution Flow and SWC Integration

The loader sequence initializes native or WebAssembly SWC Compiler capabilities before processing any file-system paths. The traversal proceeds through a strict call chain: nextAppLoader invokes installBindings, which defers to loadBindings, which triggers tryLoadWasmWithFallback and finally records telemetry via eventSwcLoadFailure.

  1. nextAppLoader initiates execution by calling installBindings() to guarantee compiler bindings are accessible early. Sources: packages/next/src/build/webpack/loaders/next-app-loader/index.ts:867-872

  2. installBindings delegates execution to loadBindings(useWasmBinary) from the compiler module, ensuring environment variables like NODE_ENV are locked before module loading. Sources: packages/next/src/build/swc/install-bindings.ts:16-23

  3. loadBindings evaluates platform compatibility and attempts to load native bindings or routes execution through tryLoadWasmWithFallback. Sources: packages/next/src/build/swc/index.ts:198-256

  4. tryLoadWasmWithFallback attempts to initialize WebAssembly bindings or download fallback packages on demand when native binaries are unavailable. Sources: packages/next/src/build/swc/index.ts:331-379

  5. eventSwcLoadFailure records failure metrics and loading status payloads (enabled, fallback, or failed) via the telemetry storage provider. Sources: packages/next/src/telemetry/events/swc-load-failure.ts:21-71

Warning

When executing via Turbopack in a subprocess, installBindings cannot pass the useWasmBinary flag because Turbopack runtime environments do not support WebAssembly compilation binaries. Sources: packages/next/src/build/webpack/loaders/next-app-loader/index.ts:867-872

Segment Resolution and Path Normalization

next-app-loader extracts loader configurations, resolves application paths, and parses route segments to build the layout and page hierarchy. It uses absolute paths computed against the root app directory and parses base64-encoded proxy configurations for middleware routing.

const loaderOptions = this.getOptions()
const {
  name,
  appDir,
  appPaths,
  allNormalizedAppPaths: allNormalizedAppPathsOption,
  pagePath,
  pageExtensions,
  rootDir,
  tsconfigPath,
  isDev,
  nextConfigOutput,
  preferredRegion,
  basePath,
  middlewareConfig: middlewareConfigBase64,
} = loaderOptions
 
const isGlobalNotFoundEnabled = !!loaderOptions.isGlobalNotFoundEnabled
const explicitParallelRouteChildren =
  !!loaderOptions.explicitParallelRouteChildren
const strictRouteMatching = !!loaderOptions.strictRouteMatching
const isFinalRouteMatcher = !!loaderOptions.isFinalRouteMatcher
 
// Update FILE_TYPES on the very top-level of the loader
if (!isGlobalNotFoundEnabled) {
  // @ts-expect-error this delete is only necessary while experimental
  delete FILE_TYPES['global-not-found']
}
 
const buildInfo = getModuleBuildInfo((this as any)._module)
const collectedDeclarations: [string, string][] = []
 
// Use the page from loaderOptions directly instead of deriving it from name.
// The name (bundlePath) may have been normalized with normalizePagePath()
// which is designed for Pages Router and incorrectly duplicates /index paths
// (e.g., /index/page -> /index/index/page). The page parameter contains the
// correct unnormalized value.
const page = loaderOptions.page
 
const middlewareConfig: ProxyConfig = JSON.parse(
  Buffer.from(middlewareConfigBase64, 'base64').toString()
)
buildInfo.route = {
  page,
  absolutePagePath: createAbsolutePath(appDir, pagePath),
  preferredRegion,
  middlewareConfig,
  relatedModules: [],
}

Sources: packages/next/src/build/webpack/loaders/next-app-loader/index.ts:873-921

The segment resolver resolveParallelSegments scans normalized app paths, identifying parallel route slots starting with @ characters and mapping pages to children segments.

const resolveParallelSegments = (
  pathname: string
): [string, string | string[]][] => {
  const matched: Record<string, string | string[]> = {}
  let existingChildrenPath: string | undefined
  for (const appPath of normalizedAppPaths) {
    if (appPath.startsWith(pathname + '/')) {
      const rest = appPath.slice(pathname.length + 1).split('/')
 
      // It is the actual page, mark it specially.
      if (rest.length === 1 && rest[0] === 'page') {
        existingChildrenPath = appPath
        matched.children = PAGE_SEGMENT
        continue
      }
 
      const isParallelRoute = rest[0].startsWith('@')
      if (isParallelRoute) {
        if (rest.length === 2 && rest[1] === 'page') {
          // We found a parallel route at this level. We don't want to mark it explicitly as the page segment,
          // as that should be matched to the `children` slot. Instead, we use an array, to signal to `createSubtreePropsFromSegmentPath`
          // that it needs to recursively fill in the loader tree code for the parallel route at the appropriate levels.
          matched[rest[0]] = [PAGE_SEGMENT]
          continue
        }
        // If it was a parallel route but we weren't able to find the page segment (ie, maybe the page is nested further)
        // we first insert a special marker to ensure that we still process layout/default/etc at the slot level prior to continuing
        // on to the page segment.
        matched[rest[0]] = [PARALLEL_VIRTUAL_SEGMENT, ...rest.slice(1)]
        continue
      }
 
      if (existingChildrenPath && matched.children !== rest[0]) {
        // If we get here, it means we already set a `page` segment earlier in the loop,
        // meaning we already matched a page to the `children` parallel segment.
        const isIncomingParallelPage = appPath.includes('@')
        const hasCurrentParallelPage = existingChildrenPath.includes('@')
 
        if (isIncomingParallelPage) {
          // The duplicate segment was for a parallel slot. In this case,
          // rather than throwing an error, we can ignore it since this can happen for valid reasons.
          // For example, when we attempt to normalize catch-all routes, we'll push potential slot matches so
          // that they are available in the loader tree when we go to render the page.
          // We only need to throw an error if the duplicate segment was for a regular page.
          // For example, /app/(groupa)/page & /app/(groupb)/page is an error since it corresponds
          // with the same path.
          continue
        } else if (!hasCurrentParallelPage && !isIncomingParallelPage) {
          // Both the current `children` and the incoming `children` are regular pages.
          throw new Error(
            `You cannot have two parallel pages that resolve to the same path. Please check ${existingChildrenPath} and ${appPath}. Refer to the route group docs for more information: https://nextjs.org/docs/app/building-your-application/routing/route-groups`
          )
        }
      }
 
      existingChildrenPath = appPath
      matched.children = rest[0]
    }
  }
 
  return Object.entries(matched)
}

Sources: packages/next/src/build/webpack/loaders/next-app-loader/index.ts:933-994

Option NameTarget PropertyPurpose
namestringBundle identifier for the target route entrypoint
appDirstringAbsolute filesystem path pointing to the app/ root directory
appPathsstring | string[]Normalized application path arrays mapped to the current segment
pagePathstringRelative filesystem path to the target page component file
pageExtensionsstring | string[]Valid file extensions recognized for route resolution
tsconfigPathstringPath to the TypeScript configuration file for path aliases
isDevbooleanCompilation mode flag indicating active development environment

Sources: packages/next/src/build/webpack/loaders/next-app-loader/index.ts:873-889

React Server Component boundary transforms

Identifying RSC Boundaries and Module Types

Next.js utilizes next-flight-loader alongside entry loaders to analyze source files during Webpack compilation, populating build info with RSC module metadata via getRSCModuleInformation. The loader classifies modules into specific types and handles source type resolution through getAssumedSourceType, mapping Webpack module types (javascript/auto, javascript/dynamic, javascript/esm) to assumed source types (auto, script, module, commonjs).

export function getAssumedSourceType(
  mod: webpack.Module,
  sourceType: SourceType
): SourceType {
  const buildInfo = getModuleBuildInfo(mod)
  const detectedClientEntryType = buildInfo?.rsc?.clientEntryType
  const clientRefs = buildInfo?.rsc?.clientRefs || []
 
  // It's tricky to detect the type of a client boundary, but we should always
  // use the `module` type when we can, to support `export *` and `export from`
  // syntax in other modules that import this client boundary.
 
  if (sourceType === 'auto') {
    if (detectedClientEntryType === 'auto') {
      if (clientRefs.length === 0) {
        // If there's zero export detected in the client boundary, and it's the
        // `auto` type, we can safely assume it's a CJS module because it doesn't
        // have ESM exports.
        return 'commonjs'
      } else if (!clientRefs.includes('*')) {
        // Otherwise, we assume it's an ESM module.
        return 'module'
      }
    } else if (detectedClientEntryType === 'cjs') {
      return 'commonjs'
    }
  }
 
  return sourceType
}

Sources: packages/next/src/build/webpack/loaders/next-flight-loader/index.ts:25-54

Module TypeAssumed Source TypeHandling Behavior
javascript/autocommonjs / moduleFallbacks to commonjs when clientRefs is empty and detectedClientEntryType is auto; otherwise assumes module.
javascript/dynamicscriptTreated as a dynamic script module.
javascript/esmmoduleForces ESM module parsing to support export syntax.

Sources: packages/next/src/build/webpack/loaders/next-flight-loader/index.ts:25-54, packages/next/src/build/webpack/loaders/next-flight-loader/index.ts:225-236

Caution

Using export * inside a client boundary is strictly unsupported. When clientRefs contains *, next-flight-loader immediately aborts compilation by passing an Error to this.callback(). Sources: packages/next/src/build/webpack/loaders/next-flight-loader/index.ts:114-121

Client Entry and Server Action Transformation

When a module is identified as a client boundary (RSC_MODULE_TYPES.client), next-flight-loader generates either an ESM proxy wrapping references with registerClientReference from "react-server-dom-webpack/server" or a CommonJS proxy using createProxy from MODULE_PROXY_PATH.

      let esmSource =
        prefix +
        `\
import { registerClientReference } from "react-server-dom-webpack/server";
`
      for (const ref of clientRefs) {
        if (ref === 'default') {
          esmSource += `export default registerClientReference(
function() { throw new Error(${JSON.stringify(`Attempted to call the default \
export of ${stringifiedResourceKey} from the server, but it's on the client. \
It's not possible to invoke a client function from the server, it can only be \
rendered as a Component or passed to props of a Client Component.`)}); },
${stringifiedResourceKey},
"default",
);\n`
        } else {
          esmSource += `export const ${ref} = registerClientReference(
function() { throw new Error(${JSON.stringify(`Attempted to call ${ref}() from \
the server but ${ref} is on the client. It's not possible to invoke a client \
function from the server, it can only be rendered as a Component or passed to \
props of a Client Component.`)}); },
${stringifiedResourceKey},
${JSON.stringify(ref)},
);`
        }
      }

Sources: packages/next/src/build/webpack/loaders/next-flight-loader/index.ts:123-148

For client entry bundles, next-flight-client-entry-loader processes incoming module definitions and server options to construct eager Webpack imports. It filters out CSS files during server-side rendering (SSR) compilation and applies explicit webpackExports optimization flags when specific identifiers are targeted.

export default function transformSource(
  this: webpack.LoaderContext<NextFlightClientEntryLoaderOptions>
) {
  let { modules, server } = this.getOptions()
  const isServer = server === 'true'
 
  if (!Array.isArray(modules)) {
    modules = modules ? [modules] : []
  }
 
  const code = modules
    .map((x) => JSON.parse(x) as FlightClientEntryModuleItem)
    // Filter out CSS files in the SSR compilation
    .filter(({ request }) => (isServer ? !regexCSS.test(request) : true))
    .map(({ request, ids }: FlightClientEntryModuleItem) => {
      const importPath = JSON.stringify(
        request.startsWith(BARREL_OPTIMIZATION_PREFIX)
          ? request.replace(':', '!=!')
          : request
      )
 
      // When we cannot determine the export names, we use eager mode to include the whole module.
      // Otherwise, we use eager mode with webpackExports to only include the necessary exports.
      // If we have '*' in the ids, we include all the imports
      if (ids.length === 0 || ids.includes('*')) {
        return `import(/* webpackMode: "eager" */ ${importPath});\n`
      } else {
        return `import(/* webpackMode: "eager", webpackExports: ${JSON.stringify(
          ids
        )} */ ${importPath});\n`
      }
    })
    .join(';\n')

Sources: packages/next/src/build/webpack/loaders/next-flight-client-entry-loader.ts:28-60

Sources: packages/next/src/build/webpack/loaders/next-flight-loader/index.ts:99-223

Additionally, next-flight-action-entry-loader parses action lists to produce dedicated re-exports for server actions, exposing reference IDs distinctly through next-flight-server-reference-proxy-loader?id=... proxies during production builds.

function nextFlightActionEntryLoader(
  this: webpack.LoaderContext<NextFlightActionEntryLoaderOptions>
) {
  const { actions }: NextFlightActionEntryLoaderOptions = this.getOptions()
 
  const actionList = JSON.parse(actions) as FlightActionEntryLoaderActions
  const individualActions = actionList
    .map(([path, actionsFromModule]) => {
      return actionsFromModule.map(({ id, exportedName }) => {
        return [id, path, exportedName] as const
      })
    })
    .flat()
 
  return `
${individualActions
  .map(([id, path, exportedName]) => {
    // Re-export the same functions from the original module path as action IDs.
    return `export { ${exportedName} as "${id}" } from ${JSON.stringify(path)}`
  })
  .join('\n')}
`
}

Sources: packages/next/src/build/webpack/loaders/next-flight-action-entry-loader.ts:12-34

SWC compilation and worker execution

Transpilation and Binding Execution

Transpilation execution is handled through SWC Compiler bindings integrated via next-swc-loader. The loader checks whether source files require transpilation by applying exclusions and verifying conditions such as next/font, next/dynamic, use server, use client, or use cache. When a file requires processing, next-swc-loader calls getLoaderSWCOptions to construct compiler options and invokes transform.

async function loaderTransform(
  this: LoaderContext<SWCLoaderOptions> & TelemetryLoaderContext,
  source?: string,
  inputSourceMap?: any
) {
  // Make the loader async
  const filename = this.resourcePath
 
  // Ensure `.d.ts` are not processed.
  if (filename.endsWith('.d.ts')) {
    return [source, inputSourceMap]
  }
 
  let loaderOptions: SWCLoaderOptions = this.getOptions() || {}
  const shouldMaybeExclude = maybeExclude(
    filename,
    loaderOptions.transpilePackages || []
  )
 
  const trackDynamicImports = shouldTrackDynamicImports(loaderOptions)
 
  if (shouldMaybeExclude) {
    if (!source) {
      throw new Error(`Invariant might be excluded but missing source`)
    }
 
    const forceTranspileConditions = trackDynamicImports
      ? FORCE_TRANSPILE_CONDITIONS_WITH_IMPORT
      : FORCE_TRANSPILE_CONDITIONS
 
    if (!forceTranspileConditions.test(source)) {
      return [source, inputSourceMap]
    }
  }

Sources: packages/next/src/build/webpack/loaders/next-swc-loader.ts:91-124

Native or WebAssembly bindings are loaded and managed through a step-by-step resolution sequence. This pipeline begins when an entry wrapper invokes native installation:

  1. nextAppLoader — Executes initial setup and calls binding installation. Sources: packages/next/src/build/webpack/loaders/next-app-loader/index.ts:867-872
  2. installBindings — Lazily requires [[SWC Compiler]] index module to defer NODE_ENV evaluation. Sources: packages/next/src/build/swc/install-bindings.ts:16-23
  3. loadBindings — Asynchronously checks platform triples, manages pending promises, and attempts native or WASM binary loading. Sources: packages/next/src/build/swc/index.ts:198-302
  4. loadNative — Searches local built directories or node_modules for platform-specific .node binaries, checking version mismatches. Sources: packages/next/src/build/swc/index.ts:1571-1616
  5. bindingToApi — Wraps raw native functions into an API object implementing subscription iterators and project option serializers. Sources: packages/next/src/build/swc/index.ts:503-506
  6. ProjectImpl — Manages native project instances, worker scheduling, compilation subscriptions, and HMR updates. Sources: packages/next/src/build/swc/index.ts:687-700

Sources: packages/next/src/build/webpack/loaders/next-app-loader/index.ts:867-872, packages/next/src/build/swc/install-bindings.ts:16-23, packages/next/src/build/swc/index.ts:198-302, packages/next/src/build/swc/index.ts:1571-1616, packages/next/src/build/swc/index.ts:503-506, packages/next/src/build/swc/index.ts:687-700

Telemetry Hooks and Options Handling

When transformations complete, telemetry loader contexts are updated using output metrics from transform results. If binding loading fails, failure events collect runtime configuration, platform identifiers, and installed package data.

export async function eventSwcLoadFailure(
  event?: Pick<
    EventSwcLoadFailure['payload'],
    'wasm' | 'nativeBindingsErrorCode'
  >
): Promise<void> {
  const telemetry: Telemetry | undefined = traceGlobals.get('telemetry')
  // can't continue if telemetry isn't set
  if (!telemetry) return
 
  let glibcVersion
  let installedSwcPackages
 
  try {
    // @ts-ignore
    glibcVersion = process.report?.getReport().header.glibcVersionRuntime
  } catch {}
 
  try {
    const pkgNames = Object.keys(optionalDependencies || {}).filter((pkg) =>
      pkg.startsWith('@next/swc')
    )
    const installedPkgs = []
 
    for (const pkg of pkgNames) {
      try {
        const { version } = require(`${pkg}/package.json`)
        installedPkgs.push(`${pkg}@${version}`)
      } catch {}
    }
 
    if (installedPkgs.length > 0) {
      installedSwcPackages = installedPkgs.sort().join(',')
    }
  } catch {}
 
  telemetry.record({
    eventName: EVENT_PLUGIN_PRESENT,
    payload: {
      nextVersion: process.env.__NEXT_VERSION as string,
      glibcVersion,
      installedSwcPackages,
      arch: process.arch,
      platform: process.platform,
      nodeVersion: process.versions.node,
      wasm: event?.wasm,
      nativeBindingsErrorCode: event?.nativeBindingsErrorCode,
    },
  })
  // ensure this event is flushed before process exits
  await telemetry.flush()
}

Sources: packages/next/src/telemetry/events/swc-load-failure.ts:21-72

Telemetry Payload FieldTypeDescription
platformstringOperating system platform (process.platform).
archstringCPU architecture (process.arch).
nodeVersionstringNode.js version string (process.versions.node).
nextVersionstringNext.js version from process.env.__NEXT_VERSION.
wasm'enabled' | 'fallback' | 'failed'Status of WebAssembly binary execution or fallback usage.
glibcVersionstringRuntime glibc version retrieved via process reporting.
installedSwcPackagesstringSorted comma-separated list of installed @next/swc package versions.
nativeBindingsErrorCodestringError code thrown during native binding load attempts.

Sources: packages/next/src/telemetry/events/swc-load-failure.ts:7-19

Warning

Failing to load SWC bindings does not call process.exit() directly inside synchronous fallback paths; errors are propagated to the caller's error handler so that telemetry buffers can flush properly before process termination. Sources: packages/next/src/build/swc/index.ts:389-394, packages/next/src/telemetry/events/swc-load-failure.ts:70-72

Pages and edge entry wrappers

Route Generation and Loader Options

The route loaders generate entrypoints and adapters across server and Edge runtime targets. The next-route-loader builds loader entries for standard PAGES and PAGES_API routes using base64-encoded middleware configurations and absolute paths.

type RouteLoaderOptionsPagesAPIInput = {
  kind: RouteKind.PAGES_API
  page: string
  preferredRegion: string | string[] | undefined
  absolutePagePath: string
  middlewareConfig: ProxyConfig
}
 
type RouteLoaderOptionsPagesInput = {
  kind: RouteKind.PAGES
  page: string
  pages: MappedPages
  preferredRegion: string | string[] | undefined
  absolutePagePath: string
  middlewareConfig: ProxyConfig
}
 
type RouteLoaderOptionsInput =
  | RouteLoaderOptionsPagesInput
  | RouteLoaderOptionsPagesAPIInput
 

Sources: packages/next/src/build/webpack/loaders/next-route-loader/index.ts:16-36

Option FieldTarget Route KindDescription
kindPAGES | PAGES_APIDiscriminator for route type processing.
pagePAGES | PAGES_APILogical page route string.
absolutePagePathPAGES | PAGES_APIAbsolute path to the userland page file.
pagesPAGESMapped internal components including /_app and /_document.
preferredRegionPAGES | PAGES_APIPreferred deployment region or regions.
middlewareConfigPAGES | PAGES_APIProxy configuration options for middleware matching.

Sources: packages/next/src/build/webpack/loaders/next-route-loader/index.ts:16-32

Note

When compiling an instrumentation hook file via loadPages, next-route-loader appends an explicit export hoisting the register method from the userland module. Sources: packages/next/src/build/webpack/loaders/next-route-loader/index.ts:169-174

Edge Runtime and Middleware Adapters

Edge runtimes and SSR adapters use specialized loaders to set up module build info, cache handlers, and runtime wrappers. next-edge-ssr-loader swaps traditional CommonJS paths for ESM folder distributions for pages SSR'd at the edge, whereas next-edge-function-loader wraps API handlers with wrapApiHandler and the edge adapter.

function swapDistFolderWithEsmDistFolder(path: string) {
  return path.replace('next/dist/pages', 'next/dist/esm/pages')
}

Sources: packages/next/src/build/webpack/loaders/next-edge-ssr-loader/index.ts:43-45

Loader ModuleSource FileKey Runtime Setup
EdgeAppRouteLoadernext-edge-app-route-loader/index.tsSets buildInfo.nextEdgeSSR with isServerComponent and registers custom cache handlers.
edgeSSRLoadernext-edge-ssr-loader/index.tsLoads edge-ssr-app or edge-ssr entrypoints with route module options.
nextEdgeFunctionLoadernext-edge-function-loader.tsImports web globals and exports nHandler wrapping API routes with adapter.
middlewareLoadernext-middleware-loader.tsDecodes base64 matchers and builds middleware module metadata.

Sources: packages/next/src/build/webpack/loaders/next-edge-app-route-loader/index.ts:88-116, packages/next/src/build/webpack/loaders/next-edge-ssr-loader/index.ts:141-227, packages/next/src/build/webpack/loaders/next-edge-function-loader.ts:39-70, packages/next/src/build/webpack/loaders/next-middleware-loader.ts:53-84

Warning

nextEdgeFunctionLoader throws an explicit runtime error if the userland module does not export a default function when evaluating edge API functions. Sources: packages/next/src/build/webpack/loaders/next-edge-function-loader.ts:57-60

Babel fallback and React Compiler

Legacy Babel Loader Pipeline

Next.js provides a Babel loader entrypoint (nextBabelLoaderOuter) that processes source files through Babel when SWC compilation is bypassed or when custom configuration files are present. The loader evaluates file characteristics (CharacteristicsGermaneToCaching), inspecting properties such as isStandalone, isServer, isPageFile, isNextDist, hasModuleExports, hasReactCompiler, fileExt, and configFilePath to build a deterministic cache key.

interface CharacteristicsGermaneToCaching {
  isStandalone: boolean
  isServer: boolean | undefined
  isPageFile: boolean | undefined
  isNextDist: boolean
  hasModuleExports: boolean
  hasReactCompiler: boolean
  fileExt: string
  configFilePath: string | undefined
}

Sources: packages/next/src/build/babel/loader/get-config.ts:57-66

Cache CharacteristicTypePurpose
isStandalonebooleanIdentifies standalone transformation mode (e.g., standalone React Compiler pipeline).
isServerboolean | undefinedDistinguishes server-side builds from client-side bundles.
isPageFileboolean | undefinedMarks files residing within the application pages directory.
isNextDistbooleanIdentifies internal Next.js distribution modules requiring CommonJS interop.
hasModuleExportsbooleanDetects presence of module.exports string tokens in the source file.
hasReactCompilerbooleanIndicates whether React Compiler plugins are enabled and applicable.
fileExtstringExtracted file extension suffix (defaulting to 'unknown').
configFilePathstring | undefinedPath to an external Babel configuration file (.babelrc, .json, .js).

Sources: packages/next/src/build/babel/loader/get-config.ts:57-66

Note

getCacheCharacteristics skips invoking isReactCompilerRequired unless hasReactCompiler is true and shouldSkipBabel evaluates to true, avoiding expensive SWC AST parsing operations on files that do not require transformation. Sources: packages/next/src/build/babel/loader/get-config.ts:134-139

Custom Babel Configuration and Deprecation Warnings

When a custom configuration file path is provided via loaderOptions.configFile, getCustomBabelConfig reads JSON/JSON5 or JavaScript configuration formats. The loader explicitly rejects .mjs and .cjs configuration files by throwing an error.

function getCustomBabelConfig(configFilePath: string) {
  if (isJsonFile.exec(configFilePath)) {
    const babelConfigRaw = readFileSync(configFilePath, 'utf8')
    return JSON5.parse(babelConfigRaw)
  } else if (isJsFile.exec(configFilePath)) {
    return require(configFilePath)
  }
  throw new Error(
    'The Next.js Babel loader does not support .mjs or .cjs config files.'
  )
}

Sources: packages/next/src/build/babel/loader/get-config.ts:253-263

checkCustomBabelConfigDeprecation analyzes user configurations to detect plugins that can be natively migrated to SWC compiler options in next.config.js.

Custom Babel PluginCorresponding Next.js Config Key (next.config.js)
styled-components / babel-plugin-styled-componentscompiler.styledComponents
@emotion/babel-plugincompiler.emotion
relay / babel-plugin-relaycompiler.relay
react-remove-properties / babel-plugin-react-remove-propertiescompiler.reactRemoveProperties
transform-remove-console / babel-plugin-transform-remove-consolecompiler.removeConsole

Sources: packages/next/src/build/babel/loader/get-config.ts:304-332

Warning

checkCustomBabelConfigDeprecation raises soft warning logs only when custom configurations contain unsupported or migratable plugins alongside the default next/babel preset, without halting the compilation process. Sources: packages/next/src/build/babel/loader/get-config.ts:272-277, packages/next/src/build/babel/loader/get-config.ts:340-345

React Compiler Pipeline Integration

getReactCompilerLoader configures an isolated standalone Babel loader instance dedicated to executing babel-plugin-react-compiler. It resolves the compiler package, detects the installed React major version from cwd, and builds environment configurations such as enableNameAnonymousFunctions and target.

try {
  const reactPkgPath = require.resolve('react/package.json', {
    paths: [cwd],
  })
  const majorVersion: string = require(reactPkgPath).version.split('.')[0]
  if (majorVersion === '18') {
    target = majorVersion
  }

Sources: packages/next/src/build/get-babel-loader-config.ts:30-37

Caution

If babel-plugin-react-compiler cannot be resolved via require.resolve, getReactCompiler throws an explicit runtime error stating that the package is required when using the React Compiler. Sources: packages/next/src/build/get-babel-loader-config.ts:6-14

Import pruning and MDX transforms

Barrel Optimizations and Import Pruning

To optimize bundle surface area, [[Webpack Loaders]] utilizes next-barrel-loader to prune unused re-exports from barrel files. When a user imports specific names from a barrel module, the loader queries an in-memory cache (barrelTransformMappingCache) or invokes the __barrel_transform__ SWC Compiler transform to generate an export map JSON representing module identifiers, import paths, and exported names.

const barrelTransformMappingCache = new Map<
  string,
  {
    exportList: [string, string, string][]
    wildcardExports: string[]
    isClientEntry: boolean
  } | null
>()

Sources: packages/next/src/build/webpack/loaders/next-barrel-loader.ts:97-104

For wildcard exports (export * from './a'), the loader inserts __barrel_optimize__ query markers instructing Webpack to recursively process submodules until matching export targets are isolated. If a non-barrel file lacks structured export maps, the loader safely re-exports the resource path.

if (!mapping) {
  // This file isn't a barrel and we can't apply any optimizations. Let's re-export everything.
  // Since this loader accepts `names` and the request is keyed with `names`, we can't simply
  // return the original source here. That will create these imports with different names as
  // different modules instances.
  this.callback(null, `export * from ${JSON.stringify(this.resourcePath)}`)
  return
}

Sources: packages/next/src/build/webpack/loaders/next-barrel-loader.ts:281-288

Note

NextBarrelLoader calls this.clearDependencies() after resolving the mapping to prevent expensive, redundant file system watcher registrations on cached barrel subpaths. Sources: packages/next/src/build/webpack/loaders/next-barrel-loader.ts:275-280

Markdown and MDX Transformation Pipeline

Compiling Markdown and MDX files relies on mdx-rs-loader, which replaces legacy JS-based transformation pipelines with the Rust-powered mdx-rs compiler. The initialization sequence coordinates multiple binding layers and format-aware compilation options.

  1. nextAppLoader initializes the pipeline. Sources: packages/next/src/build/webpack/loaders/next-app-loader/index.ts:867-872
  2. installBindings triggers binding preparation. Sources: packages/next/src/build/swc/install-bindings.ts:16-23
Was this page helpful?