nextjs-cms

Section Loading

How SectionFactory discovers, imports, validates, caches, and serves section configs, and how section classes are built.

Code: packages/nextjs-cms/src/core/factories/section-factory-with-jiti.ts, packages/nextjs-cms/src/core/sections/

From file to config

SectionFactory.loadAllSections()
  ├─ glob('**/*.section.ts', { cwd: sections.path })
  ├─ for each file:
  │    ├─ jiti(absPath).default          transpile + import at runtime
  │    ├─ reject arrays / missing default export
  │    ├─ wrap build() so instances know their configFile
  │    └─ collect errors per file (import errors, bad extensioned imports...)
  ├─ validate names (unique, case-insensitive)  → section-name-validation.ts
  ├─ strict mode? any error → return []
  └─ cache the promise (allSectionsPromise) until files or config change

The section helpers (hasItemsSection() etc.) already validated each config with Zod when the file executed, so by the time the factory sees a config it's structurally sound.

Public API

MethodUsed byReturns
getSections(type?)Admin internalsAll section configs (optionally filtered by type), with logging and the dev watcher
getSectionsSilently(type?)CLISame, without logging or watchers
getSectionsForAdmin({ type, admin })Navigation, pagesSections the admin has a privilege for, grouped by type, plus fixed pages (admins)
getSection({ name, type })InternalsOne config
getSectionForAdmin({ name, type, admin })Pages, submitOne config, only if the admin has the privilege (and requiredRole operation)

All return configs. Callers call .build() to get a Section instance when they need fields.

Section classes

Section (abstract)                   core/sections/section.ts
├── SimpleSection                    type 'simple'     title: MultilingualString
├── HasItemsSection                  type 'has_items'  headingField, search, browse, coverPhotoField...
└── CategorySection                  type 'category'   depth, allowRecursiveDelete (+ parent_id, level)

The Section constructor:

  1. Resolves the gallery config with default column names.
  2. Injects the auto-increment id identifier when db.identifier is missing (prepending it to fields and the first field group).
  3. Verifies the identifier is a text or number field.
  4. Validates unique field names.
  5. Resolves db (defaults primaryKey to [identifier], adds the identifier to unique).

Fields are not built in the constructor. Call section.buildFields() to create Field instances (section.fields, section.fieldGroups); accessing fields before that throws a descriptive error.

CategorySection appends parent_id (matching the identifier type) and level number fields when depth > 1, on a copied array so repeated build() calls don't accumulate them.

Helper functions vs. classes

export function hasItemsSection(section: HasItemsSectionOptions): HasItemsSectionConfig {
    // 1. optionsSchema.safeParse(section)  → throws "[Section: name]: ..." with a prettified Zod error
    // 2. split fields vs. field groups (first entry decides)
    // 3. validateFieldNamesForSection()    → reserved `id`, duplicates
    // 4. return { ...section, fields, fieldGroups, type, build: () => new HasItemsSection(...) }
}

Keep this split when adding options: validation in the helper (runs once at load), resolution in the class (runs per build).

Hot reload

In development, startWatcher() watches **/*.section.ts with chokidar. On change it:

  1. Resets the jiti instance (jiti's module cache can hold stale transpiled code).
  2. Clears the cached sections promise.
  3. Rewrites the hot marker components/form/helpers/_section-hot-reload.js in the app, so Next.js Fast Refresh re-renders open forms.

The CLI uses getSectionsSilently(), which never starts a watcher.

Why jiti's sync API

The loader calls jiti(absPath) (the synchronous, deprecated API) rather than await jiti.import() because the async API doesn't work under Turbopack. Section files are small, so the sync transpile cost is negligible and cached (fsCache, moduleCache).

Error reporting

Errors are collected per file into sectionProcessingErrors / sectionFetchingErrors and printed together, with targeted hints. For example, importing another section with an extension (./other.section.ts) is detected and explained, because the bundled import can't resolve it.