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

Project Structure

Last updated Oct 12, 2026100% grounded
Relevant source files

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

Overview

The Next.js repository is structured as a monorepo containing standalone web applications, core core compiler and runtime packages, and specialized developer utilities. This architecture partitions code boundaries between the main runtime engine, build-time asset tracing and analysis subsystems, internal routing layers, and developer-facing overlays.

Sources: apps/bundle-analyzer/app/page.tsx:1-5, packages/next/src/build/turborepo-access-trace/index.ts:1-5

Repository Root and Workspace Layout

Structural Search Configuration

The repository root houses configuration infrastructure governing structural analysis and pattern matching across the monorepo workspace. Specifically, the configuration file sgconfig.yml defines paths for structural search rules, tests, and utility directories.

# yaml-language-server: $schema=https://raw.githubusercontent.com/ast-grep/ast-grep/main/schemas/project.json
 
ruleDirs:
  - .config/ast-grep/rules
testConfigs:
  - testDir: .config/ast-grep/rule-tests
utilDirs:
  - .config/ast-grep/rule-utils

Sources: sgconfig.yml:1-8

The ast-grep configuration maps custom rule definitions and verification suites to designated directory paths under the workspace root.

Configuration KeyTarget PathPurpose
ruleDirs.config/ast-grep/rulesHouses structural search and lint rules
testConfigs[0].testDir.config/ast-grep/rule-testsLocates test suites validating ast-grep rules
utilDirs.config/ast-grep/rule-utilsContains shared utility scripts for custom rules
ruleDirs:
  - .config/ast-grep/rules
testConfigs:
  - testDir: .config/ast-grep/rule-tests
utilDirs:
  - .config/ast-grep/rule-utils

Sources: sgconfig.yml:3-8

Note

The ast-grep language server schema is explicitly bound via the YAML comment header pointing to the official remote JSON schema definition.

# yaml-language-server: $schema=https://raw.githubusercontent.com/ast-grep/ast-grep/main/schemas/project.json

Sources: sgconfig.yml:1-1

Standalone Applications in Apps

Component Layer and UI Architecture

The apps workspace tree houses independent internal web utilities designed to inspect and diagnose build outputs, such as the bundle-analyzer application. This standalone application partitions its architecture into web components, view controllers, binary parsing engines, and layout utilities to process Next.js build module graphs and treemaps.

The bundle analyzer web application exports core visualization and navigation components from its module index, including import chains, route typeaheads, treemap visualizers, and error states.

export { ImportChain } from './import-chain'
export { RouteTypeahead } from './route-typeahead'
export { TreemapVisualizer } from './treemap-visualizer'
export { ErrorState } from './error-state'

Sources: apps/bundle-analyzer/components/index.ts:1-4

The application provides distinct entrypoints for single build analysis and multi-build comparison via SingleAnalyzer and CompareAnalyzer wrapper components, which wrap controllers in boundary error handlers with custom fallback skeletons.

export function SingleAnalyzer() {
  return (
    <AnalyzerBoundary defaultView={CompareView.Treemap}>
      <SingleAnalyzerController />
    </AnalyzerBoundary>
  )
}
 
function SingleAnalyzerController() {
  const model = useAnalyzerModel(false)
  return <SingleAnalyzerView model={model} />
}
 
export function CompareAnalyzer() {
  return (
    <AnalyzerBoundary defaultView={CompareView.Treemap}>
      <CompareAnalyzerController />
    </AnalyzerBoundary>
  )
}
 
function CompareAnalyzerController() {
  const model = useAnalyzerModel(true)
  return <CompareAnalyzerView model={model} />
}

Sources: apps/bundle-analyzer/components/analyzer.tsx:60-84

Note

The top-level hooks read URL search parameters before rendering data hooks to allow static prerendering bailout in Next.js router navigation.

function useAnalyzerModel(compare: boolean) {
  // Read the URL before suspense data so static prerendering can bail out.
  useSearchParams()

Sources: apps/bundle-analyzer/components/analyzer.tsx:106-108

Binary Parsing and Data Models

The analysis pipeline processes low-level binary artifacts generated by Rust build analyzers using custom data readers. The ModulesData and AnalyzeData classes parse JSON header payloads followed by binary DataView buffers to resolve module paths, dependencies, dependents, and chunk parts without loading entire trees into high-level object graphs.

export class ModulesData {
  private modulesHeader: ModulesDataHeader
  private modulesBinaryData: DataView
  private pathToModuleIndex: Map<string, ModuleIndex[]>
 
  constructor(modulesArrayBuffer: ArrayBuffer) {
    // Parse modules.data
    const modulesDataView = new DataView(modulesArrayBuffer)
    const modulesJsonLength = modulesDataView.getUint32(0, false)
    const modulesJsonBytes = new Uint8Array(
      modulesArrayBuffer,
      4,
      modulesJsonLength
    )
    const modulesJsonString = new TextDecoder('utf-8').decode(modulesJsonBytes)
    this.modulesHeader = JSON.parse(modulesJsonString) as ModulesDataHeader
    const modulesBinaryOffset = 4 + modulesJsonLength
    this.modulesBinaryData = new DataView(
      modulesArrayBuffer,
      modulesBinaryOffset
    )
 
    // Build pathToModuleIndex map
    this.pathToModuleIndex = new Map()
    for (let i = 0; i < this.modulesHeader.modules.length; i++) {
      const module = this.modulesHeader.modules[i]
      const existing = this.pathToModuleIndex.get(module.path)
      if (existing) {
        existing.push(i)
      } else {
        this.pathToModuleIndex.set(module.path, [i])
      }
    }
  }

Sources: apps/bundle-analyzer/lib/analyze-data.ts:62-95

To maintain structural integrity when cross-referencing route chunks against global build modules, the application asserts matching index fingerprints between data bundles.

export function assertMatchingModuleIndex(
  modules: ModulesData,
  route: AnalyzeData,
  side: string
): void {
  const moduleHash = modules.moduleIndexHash()
  const routeHash = route.moduleIndexHash()
  if (moduleHash !== routeHash) {
    throw new Error(`${side}: analyzer module-index fingerprint mismatch`)
  }
}

Sources: apps/bundle-analyzer/lib/analyze-data.ts:216-226

Data Structure / ClassResponsibilitiesKey Methods
ModulesDataParses global module graphs, dependency edges, and path lookupsmodule(), moduleDependents(), moduleDependencies()
AnalyzeDataParses route-specific chunk parts, output files, and source treessource(), chunkPart(), outputFile(), sourceRoots()
FileNodeRepresents individual files in the treemap modelProperties: id, name, size, outputType, server, client
DirectoryNodeRepresents container directories grouping child nodesProperties: id, name, type, children, size
export class ModulesData {
  private modulesHeader: ModulesDataHeader
  private modulesBinaryData: DataView
  private pathToModuleIndex: Map<string, ModuleIndex[]>
 
  constructor(modulesArrayBuffer: ArrayBuffer) {
    // Parse modules.data
    const modulesDataView = new DataView(modulesArrayBuffer)
    const modulesJsonLength = modulesDataView.getUint32(0, false)
    const modulesJsonBytes = new Uint8Array(
      modulesArrayBuffer,
      4,
      modulesJsonLength
    )
    const modulesJsonString = new TextDecoder('utf-8').decode(modulesJsonBytes)
    this.modulesHeader = JSON.parse(modulesJsonString) as ModulesDataHeader
    const modulesBinaryOffset = 4 + modulesJsonLength
    this.modulesBinaryData = new DataView(
      modulesArrayBuffer,
      modulesBinaryOffset
    )
 
    // Build pathToModuleIndex map
    this.pathToModuleIndex = new Map()
    for (let i = 0; i < this.modulesHeader.modules.length; i++) {
      const module = this.modulesHeader.modules[i]
      const existing = this.pathToModuleIndex.get(module.path)
      if (existing) {
        existing.push(i)
      } else {
        this.pathToModuleIndex.set(module.path, [i])
      }
    }
  }
 
  moduleIndexHash(): string {
    const hash = this.modulesHeader.module_index_hash
    if (typeof hash !== 'string' || !hash) {
      throw new Error('Missing analyzer module-index fingerprint')
    }
    return hash
  }
 
  module(index: ModuleIndex): AnalyzeModule | undefined {
    return this.modulesHeader.modules[index]
  }
 
  moduleCount(): number {
    return this.modulesHeader.modules.length
  }
 
  getModuleIndiciesFromPath(path: string): ModuleIndex[] {
    return this.pathToModuleIndex.get(path) ?? []
  }
…
export interface FileNode {
  id: number
  name: string
  type: 'file'
  size: number
  outputType: 'code' | 'css' | 'asset'
  server: boolean
  client: boolean
  dependencies: number[]
  dependents: number[]
}
 
export interface DirectoryNode {
  id: number
  name: string
  type: 'directory'
  children: TreeNode[]
  size?: number
}

Sources: apps/bundle-analyzer/lib/analyze-data.ts:62-115, apps/bundle-analyzer/lib/types.ts:1-19

Warning

Joining a route analysis data payload with an unmatched global module data buffer throws a runtime fingerprint mismatch error to prevent corrupted graph traversals.

const moduleHash = modules.moduleIndexHash()
const routeHash = route.moduleIndexHash()
if (moduleHash !== routeHash) {
  throw new Error(`${side}: analyzer module-index fingerprint mismatch`)
}

Sources: apps/bundle-analyzer/lib/analyze-data.ts:221-225

Core Package Organization

App Directory and Module Resolution

The main next package tree is structured into server-side runtime infrastructure, compilation layers, testing utilities, and module resolution helpers. Layer separation isolates bundling internals from runtime routing modules and experimental test harnesses.

Server module resolution for the App Router relies on LoaderTree tuples generated by internal webpack loaders. These trees encode segments, parallel routing keys, static sibling metadata, and module definitions to load layouts, pages, and fallback states dynamically.

export type LoaderTree = [
  segment: string,
  parallelRoutes: { [parallelRouterKey: string]: LoaderTree },
  modules: AppDirModules,
  staticSiblings: readonly string[] | null,
]

Sources: packages/next/src/server/lib/app-dir-module.ts:7-29

The server retrieves layouts and page modules through getLayoutOrPageModule, evaluating segment types and default fallbacks based on the active DEFAULT_SEGMENT_KEY.

export async function getLayoutOrPageModule(loaderTree: LoaderTree) {
  const { layout, page, defaultPage } = loaderTree[2]
  const isLayout = typeof layout !== 'undefined'
  const isPage = typeof page !== 'undefined'
  const isDefaultPage =
    typeof defaultPage !== 'undefined' && loaderTree[0] === DEFAULT_SEGMENT_KEY
 
  let mod = undefined
  let modType: 'layout' | 'page' | undefined = undefined
  let filePath = undefined
 
  if (isLayout) {
    mod = await layout[0]()
    modType = 'layout'
    filePath = layout[1]
  } else if (isPage) {
    mod = await page[0]()
    modType = 'page'
    filePath = page[1]
  } else if (isDefaultPage) {
    mod = await defaultPage[0]()
    modType = 'page'
    filePath = defaultPage[1]
  }
 
  return { mod, modType, filePath }
}

Sources: packages/next/src/server/lib/app-dir-module.ts:31-57

Note

getComponentTypeModule accepts specific module types (layout, not-found, forbidden, unauthorized) to resolve conditional error and boundary components directly from the loader tree structure.

export async function getComponentTypeModule(
  loaderTree: LoaderTree,
  moduleType: 'layout' | 'not-found' | 'forbidden' | 'unauthorized'
) {
  const { [moduleType]: module } = loaderTree[2]
  if (typeof module !== 'undefined') {
    return await module[0]()
  }
  return undefined
}

Sources: packages/next/src/server/lib/app-dir-module.ts:59-68

Experimental Testing and Shared Bundles

The package exposes experimental testing entry points for server configurations and middleware behaviors, alongside specialized routing query utilities.

Export ModuleSource PathResponsibilities
config-testing-utils./config-testing-utilsTesting utilities for server configuration options
middleware-testing-utils./middleware-testing-utilsTesting harnesses for middleware request execution
Route Utilities./utilsExports getRedirectUrl, getRewrittenUrl, and isRewrite
export * from './config-testing-utils'
export * from './middleware-testing-utils'
export { getRedirectUrl, getRewrittenUrl, isRewrite } from './utils'

Sources: packages/next/src/experimental/testing/server/index.ts:1-3

Shared app-route runtime modules re-export context shims such as appRouterContext to maintain compatibility with navigation hooks imported from next/navigation.

// the name of the export has to be the camelCase version of the file name (without the extension)
// TODO: remove this. We need it because using notFound from next/navigation imports this file :(
export * as appRouterContext from '../../../shared/lib/app-router-context.shared-runtime'

Sources: packages/next/src/server/route-modules/app-route/shared-modules.ts:1-3

Build System and Asset Tracing

Build Adapters and File Tracing

The Next.js build infrastructure includes asset tracing modules that process .nft.json trace files generated by bundlers like Webpack and Turbopack. The mapNftFileEntries helper parses NftJson structures, mapping source paths against base roots and additional roots while managing symlinks and root escapes.

export interface MappedNftFileEntry {
  source: string
  destination: string
  hash?: string
  symlinkTarget?: string
  symlinkCrossesRoot: boolean
}

Sources: packages/next/src/build/nft.ts:112-123

When building outputs, pushAsset records assets and generates SHA-256 hashes when utilizing Turbopack. File hashing inspects symlinks via fs.readlink or falls back to reading file buffers directly with a salt prefix.

Helper FunctionPurpose
loadNFTParses .nft.json payloads and registers mapped entries
hashFileComputes a salted SHA-256 hash for files or symlink targets
hashLinkTargetGenerates a salted SHA-256 hash for link target strings
resolveNftOutputPathVerifies that destination paths remain inside deployment roots
async function pushAsset(
  assets: Record<string, string>,
  assetsHashes: Record<string, string>,
  targetFilePath: string,
  sourceFilePath: string,
  bundler: Bundler,
  salt: string,
  hashOverride?: string
) {
  if (!(targetFilePath in assets)) {
    assets[targetFilePath] = sourceFilePath
    if (bundler === Bundler.Turbopack) {
      assetsHashes[targetFilePath] =
        hashOverride ?? (await hashFile(salt, sourceFilePath))
    }
  }
}
 
async function loadNFT(
  assets: Record<string, string>,
  assetsHashes: Record<string, string>,
  repoRoot: string,
  traceFilePath: string,
  syntheticSymlinks: SyntheticSymlinkManager,
  salt: string
): Promise<{ entryHash?: string }> {
  const nft = JSON.parse(await fs.readFile(traceFilePath, 'utf8')) as NftJson
 
  for (const entry of mapNftFileEntries(nft, traceFilePath, repoRoot)) {
    let source = entry.source
    let hash = entry.hash
 
    if (entry.symlinkCrossesRoot) {
      if (entry.symlinkTarget === undefined) {
        throw new InvariantError(
          `Expected cross-root symlink ${JSON.stringify(entry.destination)} to have a target`
        )
      }
      const linkTarget =
        path.relative(path.dirname(entry.destination), entry.symlinkTarget) ||
        '.'
      hash = hashLinkTarget(salt, linkTarget)
      source = syntheticSymlinks.createLink(entry.source, linkTarget, hash)
    }
 
    assets[entry.destination] = source
    if (hash) {
      assetsHashes[entry.destination] = hash
    }
  }
  return { entryHash: nft.entryHash }
}
 
async function hashFile(salt: string, filePath: string): Promise<string> {
  try {
    // Try symlink first, since readFile just transparently resolves those (or fails if it's a
    // directory symlink).
    const linkTarget = await fs.readlink(filePath)
    return hashLinkTarget(salt, linkTarget)
  } catch (e: any) {
    if (e.code === 'EINVAL') {
      // Not a symlink
      const hash = crypto.createHash('sha256')
      hash.update(salt)
      hash.update('file:')
      hash.update(await fs.readFile(filePath))
      return hash.digest('hex')
    } else {
      throw e
    }
  }
}
 
function hashLinkTarget(salt: string, linkTarget: string): string {
  const hash = crypto.createHash('sha256')
  hash.update(salt)
  hash.update('link')
  hash.update(linkTarget)
  return hash.digest('hex')

Sources: packages/next/src/build/adapter/build-complete.ts:2822-2900

Caution

If a file or symlink destination attempts to escape the deployment root during resolution, resolveNftOutputPath immediately throws an invariant error.

export function resolveNftOutputPath(
  outputRoot: string,
  destination: string
): string {
  const outputPath = path.resolve(outputRoot, destination)
  if (relativePathIfInside(outputRoot, outputPath) === undefined) {
    invalid(
      `output path ${JSON.stringify(destination)} escapes the deployment root`
    )
  }
  return outputPath
}

Sources: packages/next/src/build/nft.ts:269-280

Turborepo Access Tracing

The Turbopack and Turborepo integration exposes access tracing utilities that record file system operations during compilation and build passes. The access trace subsystem is exported via modular entry points containing result classes and helper functions.

export type { SerializableTurborepoAccessTraceResult } from './types'
export {
  turborepoTraceAccess,
  writeTurborepoAccessTraceResult,
} from './helpers'
export { TurborepoAccessTraceResult } from './result'

Sources: packages/next/src/build/turborepo-access-trace/index.ts:1-6

In parallel, build analysis graph dumping utilities (dumpAnalyzeGraph) read module indices and route snapshots to validate binary edge representations and schema versions.

export function dumpAnalyzeGraph(
  directory: string,
  snapshotName: string,
  routeFilter: string | undefined
): void {
  // Pipe stdout must block so writeSync supplies backpressure rather than EAGAIN.
  if (process.stdout._handle != null) {
    // @ts-ignore Node's internal handle, also used by the native bindings loader.
    process.stdout._handle.setBlocking?.(true)
  }

Sources: packages/next/src/build/analyze/graph-dump.ts:326-335

Developer Tooling and Overlay Subsystems

Devtools Entrypoints and Browser Overlays

The Next.js developer tooling subsystem initializes browser error overlays, build status indicators, and segment trees via entrypoints housed in packages/next/src/next-devtools. The main dev-overlay browser module (packages/next/src/next-devtools/dev-overlay.browser.tsx) exports renderAppDevOverlay and renderPagesDevOverlay, which attach shadow roots and mount the DevOverlayRoot component inside isolated nextjs-portal containers.

FunctionRouter TypeKey ParametersPurpose
renderAppDevOverlayappgetOwnerStack, isRecoverableError, enableCacheIndicator, enableRuntimeErrorReportingMounts the developer overlay for the App Router with a script container and identifier prefix ndt-
renderPagesDevOverlaypagesgetOwnerStack, getSquashedHydrationErrorDetails, isRecoverableErrorMounts the developer overlay for the Pages Router using a MutationObserver to re-attach the container if removed
export * from './dev-overlay.browser'

Sources: packages/next/src/next-devtools/entrypoint.ts:1-1

Caution

Attempting to mount the Pages developer overlay while the App Router overlay is already mounted, or vice versa, triggers an invariant error because switching between router types requires a hard navigation.

if (isPagesMounted) {
  // Switching between App and Pages Router is always a hard navigation
  // TODO: Support soft navigation between App and Pages Router
  throw new Error(
    'Next DevTools: Pages Dev Overlay is already mounted. This is a bug in Next.js'
  )
}
 

Sources: packages/next/src/next-devtools/dev-overlay.browser.tsx:455-462

The dispatcher object processes queued events through an EventQueue instance, mapping action dispatchers for build states, version info, indicators, and runtime errors.

Dispatcher ActionAction Type ConstantDescription
onBuildOkACTION_BUILD_OKSignals successful compilation
onBuildErrorACTION_BUILD_ERRORReports build error messages
onBeforeRefreshACTION_BEFORE_REFRESHTriggers before hot module replacement refresh
onRefreshACTION_REFRESHExecutes HMR refresh
onStaticIndicatorACTION_STATIC_INDICATORUpdates static vs dynamic rendering status (pending, static, dynamic, disabled)
openErrorOverlayACTION_ERROR_OVERLAY_OPENOpens the runtime error overlay
export const dispatcher: Dispatcher = {
  onBuildOk: createQueuable((dispatch: Dispatch) => {
    dispatch({ type: ACTION_BUILD_OK })
  }),
  onBuildError: createQueuable((dispatch: Dispatch, message: string) => {
    dispatch({ type: ACTION_BUILD_ERROR, message })
  }),
  onBeforeRefresh: createQueuable((dispatch: Dispatch) => {
    dispatch({ type: ACTION_BEFORE_REFRESH })
  }),
  onRefresh: createQueuable((dispatch: Dispatch) => {
    dispatch({ type: ACTION_REFRESH })
  }),

Sources: packages/next/src/next-devtools/dev-overlay.browser.tsx:203-215

Segment Trees and User-Space Hooks

The dev overlay includes a trie data structure located in packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.ts for storing and searching App Router segment paths. The trie utilizes null-prototype child objects (Object.create(null)) so that segment names colliding with built-in Object.prototype members like constructor or toString resolve correctly.

type TrieNode<Value = string> = {
  value: Value | undefined
  children: {
    [key: string]: TrieNode<Value> | undefined
  }
}

Sources: packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.ts:21-26

User-space integration hooks are provided in packages/next/src/next-devtools/userspace/app/segment-explorer-node.tsx, exporting boundary components and state providers.

Exported Component / HookPurpose
SegmentStateProviderContext provider managing boundary types (not-found, error, loading, global-error) and reload triggers
SegmentBoundaryTriggerNodeRenders simulated fallback components such as LoadingSegmentNode, NotFoundSegmentNode, or ErrorSegmentNode
SegmentViewStateNodeUpdates route state trees via the dispatcher during layout rendering
useSegmentStateConsumes segment boundary context
export function SegmentStateProvider({ children }: { children: ReactNode }) {
  const [boundaryType, setBoundaryType] = useState<SegmentBoundaryType | null>(
    null
  )
 
  const [errorBoundaryKey, setErrorBoundaryKey] = useState(0)
  const reloadBoundary = useCallback(
    () => setErrorBoundaryKey((prev) => prev + 1),
    []
  )

Sources: packages/next/src/next-devtools/userspace/app/segment-explorer-node.tsx:129-138

Note

SegmentTrieNode registers node state using useLayoutEffect rather than useEffect to ensure updates occur during React Suspense boundaries without losing mounted state.

// Use `useLayoutEffect` to ensure the state is updated during suspense.
// `useEffect` won't work as the state is preserved during suspense.
useLayoutEffect(() => {
  dispatcher.segmentExplorerNodeAdd(nodeState)
  return () => {
    dispatcher.segmentExplorerNodeRemove(nodeState)
  }
}, [nodeState])

Sources: packages/next/src/next-devtools/userspace/app/segment-explorer-node.tsx:50-57

Additional user-space error helpers are re-exported from packages/next/src/next-devtools/userspace/app/errors/index.ts, exposing console error interception, client error handling, and dev error decoration.

export { originConsoleError } from './intercept-console-error'
export { handleClientError } from './use-error-handler'
export { decorateDevError } from './stitched-error'

Sources: packages/next/src/next-devtools/userspace/app/errors/index.ts:1-3

Auxiliary Tooling Packages

Documentation Indexing and Tree Building

Satellite packages within the repository provide auxiliary capabilities such as repository automation, documentation indexing, and code transformations via packages/next-codemod. This package includes utilities for discovering documentation files, parsing directory trees, generating agent-readable indexes, and managing workspace configurations.

The codemod package contains routines in packages/next-codemod/lib/agents-md.ts that scan documentation directories to build hierarchical representations of markdown and MDX files. The file collector filters out index pages, sorts results lexicographically, and normalizes path separators for cross-platform compatibility.

export function collectDocFiles(dir: string): { relativePath: string }[] {
  return (fs.readdirSync(dir, { recursive: true }) as string[])
    .filter(
      (f) =>
        (f.endsWith('.mdx') || f.endsWith('.md')) &&
        !/[/\\]index\.mdx$/.test(f) &&
        !/[/\\]index\.md$/.test(f) &&
        !f.startsWith('index.')
    )
    .sort()
    .map((f) => ({ relativePath: f.replace(/\\/g, '/') }))
}

Sources: packages/next-codemod/lib/agents-md.ts:237-248

The tree builder organizes files into nested DocSection structures up to three levels deep, sorting sections and subsections alphabetically.

Structure PropertyTypeDescription
namestringDirectory segment name
files{ relativePath: string }[]List of documentation files contained in the section
subsectionsDocSection[]Nested child sections
interface DocSection {
  name: string
  files: { relativePath: string }[]
  subsections: DocSection[]
}

Sources: packages/next-codemod/lib/agents-md.ts:250-254

Note

collectDocFiles explicitly ignores any file whose path ends with index.md or index.mdx, or starts with index., to prevent top-level landing pages from duplicating nested route structures in agent indexes.

return (fs.readdirSync(dir, { recursive: true }) as string[])
  .filter(
    (f) =>
      (f.endsWith('.mdx') || f.endsWith('.md')) &&
      !/[/\\]index\.mdx$/.test(f) &&
      !/[/\\]index\.md$/.test(f) &&
      !f.startsWith('index.')
  )

Sources: packages/next-codemod/lib/agents-md.ts:238-245

Workspace Detection and Ignore Management

The auxiliary package inspects project environments to identify monorepo structures and ensure generated build artifacts or documentation caches are properly excluded from version control.

Workspace TypeDetection FileParsing Utility
pnpmpnpm-workspace.yamlparsePnpmWorkspace
npm / yarnpackage.jsonparsePackageJsonWorkspaces
lernalerna.jsonparseLernaConfig
nullnonenone
function detectWorkspace(cwd: string): WorkspaceInfo {
  const packageJsonPath = path.join(cwd, 'package.json')
 
  // Check pnpm workspaces (pnpm-workspace.yaml)
  const pnpmWorkspacePath = path.join(cwd, 'pnpm-workspace.yaml')
  if (fs.existsSync(pnpmWorkspacePath)) {
    const packages = parsePnpmWorkspace(pnpmWorkspacePath)
    if (packages.length > 0) {
      return { isMonorepo: true, type: 'pnpm', packages }
    }
  }
 
  // Check npm/yarn workspaces (package.json workspaces field)
  if (fs.existsSync(packageJsonPath)) {
    const packages = parsePackageJsonWorkspaces(packageJsonPath)
    if (packages.length > 0) {
      return { isMonorepo: true, type: 'npm', packages }
    }
  }
 
  // Check Lerna (lerna.json)
  const lernaPath = path.join(cwd, 'lerna.json')
  if (fs.existsSync(lernaPath)) {
    const packages = parseLernaConfig(lernaPath)
    if (packages.length > 0) {
      return { isMonorepo: true, type: 'lerna', packages }

Sources: packages/next-codemod/lib/agents-md.ts:463-488

When updating .gitignore, ensureGitignoreEntry checks for existing patterns matching .next-docs entries using regular expression tests across file lines, appending the entry and a comment header if absent.

export function ensureGitignoreEntry(cwd: string): GitignoreStatus {
  const gitignorePath = path.join(cwd, '.gitignore')
  const entryRegex = /^\s*\.next-docs(?:\/.*)?$/
 
  let content = ''
  if (fs.existsSync(gitignorePath)) {
    content = fs.readFileSync(gitignorePath, 'utf-8')
  }
 
  const hasEntry = content.split(/\r?\n/).some((line) => entryRegex.test(line))
 
  if (hasEntry) {
    return { path: gitignorePath, updated: false, alreadyPresent: true }
  }
...

Sources: packages/next-codemod/lib/agents-md.ts:428-441

Was this page helpful?