Portability

An app does not have to know it is in a monolith, but it does have to avoid a few things. Both checks below run from inside the app, so a problem is found while working on the app rather than when someone tries to mount it.

Config and conventions

Wrap the app's own config:

// apps/id/next.config.ts
import { withMonolithicPortability } from '@fairgarden/monolith'

export default withMonolithicPortability(nextConfig)

It reports config the monolith owns or that has to be shared, conventions a deployment only gets one of (proxy/middleware, instrumentation, pages/_app, pages/_document), and routes that only mean anything at the site root (robots, sitemap, manifest, which would end up at /<name>/robots.txt). See withMonolithicPortability.

Imports

A mounted app's route files are not where they appear to be, so a relative import that climbs out of app/ or pages/ no longer lands anywhere:

// apps/id/app/[locale]/page.tsx
import { defaultLocale } from '../../../lib/constants/localization'

That compiles perfectly on its own. In the monolith, Turbopack cannot resolve it from pages/, and from app/ it resolves but fails the type check. Both are integration-time failures for something a lint rule catches immediately.

Import by package name instead, which resolves from anywhere because withMonolith gives each app a self-reference:

import { defaultLocale } from '@fairgarden/id/lib/constants/localization'
// apps/id/eslint.config.mjs
import monolith from '@fairgarden/monolith/eslint'

export default [...monolith.configs.recommended]

Both apply only to files inside a route tree; a shared lib/ is left alone.