Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/build/turborepo-access-trace/index.ts
- apps/bundle-analyzer/components/index.ts
- apps/bundle-analyzer/app/page.tsx
- packages/next/src/build/swc/index.ts
- packages/next/src/build/adapter/build-complete.ts
- packages/third-parties/src/google/index.tsx
- packages/next-routing/src/index.ts
- packages/next/src/build/webpack/loaders/next-app-loader/index.ts
- apps/bundle-analyzer/components/analyzer.tsx
- packages/next/src/server/lib/app-dir-module.ts
- packages/next/src/server/dev/hot-reloader-turbopack.ts
- packages/next/src/next-devtools/dev-overlay/components/dialog/index.ts
- apps/bundle-analyzer/lib/analyze-data.ts
- packages/next-codemod/lib/agents-md.ts
- packages/next/src/build/nft.ts
- packages/next/src/server/base-server.ts
- packages/next/src/next-devtools/dev-overlay/components/overlay/index.tsx
- packages/next/src/next-devtools/entrypoint.ts
- packages/next/src/experimental/testing/server/index.ts
- packages/next/src/server/app-render/instant-validation/instant-validation.tsx
- apps/bundle-analyzer/lib/types.ts
- packages/next/src/server/route-modules/app-route/shared-modules.ts
- packages/next/src/build/analyze/graph-dump.ts
- sgconfig.yml
- packages/next/src/next-devtools/dev-overlay/segment-explorer-trie.ts
- packages/next/src/bundles/webpack/packages/package.js
- apps/bundle-analyzer/lib/module-graph.ts
- packages/next/src/next-devtools/userspace/app/errors/index.ts
- packages/next/src/next-devtools/userspace/app/segment-explorer-node.tsx
- packages/next/src/next-devtools/dev-overlay.browser.tsx
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-utilsSources: sgconfig.yml:1-8
The ast-grep configuration maps custom rule definitions and verification suites to designated directory paths under the workspace root.
| Configuration Key | Target Path | Purpose |
|---|---|---|
ruleDirs | .config/ast-grep/rules | Houses structural search and lint rules |
testConfigs[0].testDir | .config/ast-grep/rule-tests | Locates test suites validating ast-grep rules |
utilDirs | .config/ast-grep/rule-utils | Contains shared utility scripts for custom rules |
ruleDirs:
- .config/ast-grep/rules
testConfigs:
- testDir: .config/ast-grep/rule-tests
utilDirs:
- .config/ast-grep/rule-utilsSources: 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.jsonSources: 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 / Class | Responsibilities | Key Methods |
|---|---|---|
ModulesData | Parses global module graphs, dependency edges, and path lookups | module(), moduleDependents(), moduleDependencies() |
AnalyzeData | Parses route-specific chunk parts, output files, and source trees | source(), chunkPart(), outputFile(), sourceRoots() |
FileNode | Represents individual files in the treemap model | Properties: id, name, size, outputType, server, client |
DirectoryNode | Represents container directories grouping child nodes | Properties: 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 Module | Source Path | Responsibilities |
|---|---|---|
config-testing-utils | ./config-testing-utils | Testing utilities for server configuration options |
middleware-testing-utils | ./middleware-testing-utils | Testing harnesses for middleware request execution |
| Route Utilities | ./utils | Exports 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 Function | Purpose |
|---|---|
loadNFT | Parses .nft.json payloads and registers mapped entries |
hashFile | Computes a salted SHA-256 hash for files or symlink targets |
hashLinkTarget | Generates a salted SHA-256 hash for link target strings |
resolveNftOutputPath | Verifies 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.
| Function | Router Type | Key Parameters | Purpose |
|---|---|---|---|
renderAppDevOverlay | app | getOwnerStack, isRecoverableError, enableCacheIndicator, enableRuntimeErrorReporting | Mounts the developer overlay for the App Router with a script container and identifier prefix ndt- |
renderPagesDevOverlay | pages | getOwnerStack, getSquashedHydrationErrorDetails, isRecoverableError | Mounts 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 Action | Action Type Constant | Description |
|---|---|---|
onBuildOk | ACTION_BUILD_OK | Signals successful compilation |
onBuildError | ACTION_BUILD_ERROR | Reports build error messages |
onBeforeRefresh | ACTION_BEFORE_REFRESH | Triggers before hot module replacement refresh |
onRefresh | ACTION_REFRESH | Executes HMR refresh |
onStaticIndicator | ACTION_STATIC_INDICATOR | Updates static vs dynamic rendering status (pending, static, dynamic, disabled) |
openErrorOverlay | ACTION_ERROR_OVERLAY_OPEN | Opens 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 / Hook | Purpose |
|---|---|
SegmentStateProvider | Context provider managing boundary types (not-found, error, loading, global-error) and reload triggers |
SegmentBoundaryTriggerNode | Renders simulated fallback components such as LoadingSegmentNode, NotFoundSegmentNode, or ErrorSegmentNode |
SegmentViewStateNode | Updates route state trees via the dispatcher during layout rendering |
useSegmentState | Consumes 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 Property | Type | Description |
|---|---|---|
name | string | Directory segment name |
files | { relativePath: string }[] | List of documentation files contained in the section |
subsections | DocSection[] | 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 Type | Detection File | Parsing Utility |
|---|---|---|
pnpm | pnpm-workspace.yaml | parsePnpmWorkspace |
npm / yarn | package.json | parsePackageJsonWorkspaces |
lerna | lerna.json | parseLernaConfig |
null | none | none |
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
Related
On This Page
OverviewRepository Root and Workspace LayoutStructural Search ConfigurationStandalone Applications in AppsComponent Layer and UI ArchitectureBinary Parsing and Data ModelsCore Package OrganizationApp Directory and Module ResolutionExperimental Testing and Shared BundlesBuild System and Asset TracingBuild Adapters and File TracingTurborepo Access TracingDeveloper Tooling and Overlay SubsystemsDevtools Entrypoints and Browser OverlaysSegment Trees and User-Space HooksAuxiliary Tooling PackagesDocumentation Indexing and Tree BuildingWorkspace Detection and Ignore ManagementRelated