SuperAPI AI Directory

modernjs-migrate-to-v3

General & other

View repository

Updated 10-16 09:54

About this skill

Migrate Modern.js 2.0 to 3.0

This skill is used for the v2→v3 migration of a single Modern.js application. Goal: complete the safely rewritable parts, and narrow the remaining risks into a clear manual checklist. Rules and examples are based on the repository's guides/upgrade/* real documentation.

Usage principles

  • The caller first determines projectDir, all modifications are limited to projectDir
  • Do not read all of references/at the start; only load on demand when a manual item is hit
  • Commit once after each successful step ends (see references/commit-changes.md)

Output requirements

  • Progress brief:[X/6] 开始/完成/跳过/失败
  • Record non-blocking issues and continue; for blocking issues stop immediately and explain the step, reason, and recovery method

Pre-checks

git -C <projectDir> status --porcelain

Stop when the workspace is non-empty, prompt to first git commit/git stash. It is recommended to migrate on a clean branch or worktree, to facilitate rollback.

Execution steps

Step 1: Scan the project and generate the migration context

node scripts/scan-project.mjs <projectDir>

Produce <projectDir>/.agents/runs/modernjs-migrate/context.json: determine v2/v3, Node version, entry type, hit features and v2Signals. When the script fails (not v2/v3, Node too low), stop directly and show the reason.migrationState=v3 Treat as continued migration.

monorepo / workspace projects:@modern-js/app-tools When using protocols such as workspace:* / link: / catalog: , the major version cannot be determined from the version number. At this point only when hittingv2-only structural signals(top-level runtime、appTools({ bundler })、applyBaseConfig、@modern-js/plugin-tailwindcss、@modern-js/runtime/bff|server import、App.config/init、src/pages, custom server/index) is it judged as v2;if there is no signal at all, treat it as ambiguous and block(non-zero exit, do not write context), to avoid mistakenly migrating a workspace application that is already v3.routes / src/modern.runtime.ts / appTools() is not counted as a v2 signal (v3 also has it).

Convention-based routing red lines:entryType=routes Whensrc/routes/page.* and src/routes/layout.* are v3 standard structures, they must be preserved. Do not rename page.tsx to index.tsx, do not move layout.tsx to legacy-*, do not generate src/entry.* or src/legacy-app / src/legacy-routesout of thin air. If you need to handle routes/layout 's config/init export, only migrate the runtime configuration logic, do not change the routing file naming.

Step 2: Perform safely automatable rewrites

node scripts/migrate.mjs <projectDir>

Automatically complete (based on guides/upgrade/*):

  • Pre-protection:workspace/link/catalog protocol + no v2-only signals → abort directly (exit 1, do not modify any files), without relying on scan
  • Target version: when --to is not passed, the latest stable v3 version of @modern-js/app-tools@3 will be detected via npm as the target version; only pass --to=<version>explicitly when the version needs to be pinned. If the current environment cannot access the npm registry, the script will abort and prompt to explicitly pass in the target version,it will not fall back to 3.0.0。
  • Dependencies: for pinned versions (^2.x) of @modern-js/* , upgrade to the detected latest v3 target version;workspace/link/catalog protocol dependencies are kept unchanged(upgraded together with the monorepo as a whole, put into the manual checklist); remove @modern-js/plugin-tailwindcss
  • Configuration entry form:defineConfig({}) / defineConfig<'rspack'>({}) / export default {} / module.exports = {}(JS static config) are all handled as the main path; functional/dynamic defineConfig(() => ({})) if it contains runtime, goes into the manual checklist
  • import paths:@modern-js/runtime/bff→@modern-js/plugin-bff/client、@modern-js/runtime/server→@modern-js/server-runtime, andadd the corresponding dependencies(@modern-js/plugin-bff / @modern-js/server-runtime; version protocol: for regular semver use the target version;workspace: / catalog:(name-independent) reuse the existing app-tools/runtime spec;link: / file: / portal: / npm:(pointing to a specific package path/alias)do not copy verbatim, goes into the manual checklist); when BFF is matched, give modern.config top-level plugins append at the end bffPlugin()(keep [appTools(), bffPlugin()] order; when necessary, on the @modern-js/app-tools import add appTools; if it cannot be added, it goes into the manual checklist, do not write a half-finished product)
  • Robustness (comments/strings): import'sdetection and rewriting share the same real specifier scanner(eachModuleSpecifier, covering import/export-from/side-effect import/import()/require(), including import(/* magic */ '...') dynamic imports with comments like this);dev/server only at theconfig object top levelmigrate (nested tools.dev.port is not touched); tailwind only deletes real import lines and real tailwindcssPlugin() calls (by real specifier offset, without harming ordinary strings/comment examples, and cleaning up dangling commas after removal); defineConfig/runtime/appTools({ bundler })/applyBaseConfig/dev: { port }/@modern-js/runtime/bff|server in comments/ordinary strings will not be mistaken for real config, v2 signals, or imports; only judgments that need a string "value" (such as ssr.mode) keep the string
  • Config:appTools({ bundler })→appTools()(v3 defaults to Rspack, only delete the bundler parameter);modern.config top-level runtime block → merge into src/modern.runtime.ts(v3 no longer supports configuring runtime in config; only merge intoempty defineRuntimeConfig({}), non-empty/functional goes into the manual checklist without overwriting);dev.port→server.port(only move top-level port, keep the remaining fields of the dev block; nested ones such as dev.client.port are not touched); remove the tailwind plugin and write postcss.config.cjs
  • v3 config item renaming(based on guides/upgrade/config.mdx, structured item by item):output.cssModuleLocalIdentName→output.cssModules.localIdentName、disableCssExtract→injectStyles、disableFilenameHash/disableMinimize/disableSourceMap→filenameHash/minify/sourceMap(boolean negated),enableInlineScripts/enableInlineStyles→inlineScripts/inlineStyles;source.moduleScopes/enableCustomEntry/disableEntryDirs removed,resolveMainFields/resolveExtensionPrefix、output.enableLatestDecorators=true goes to manual;html.appIcon string→{ icons:[{src,size:180}] }、disableHtmlFolder→outputStructure、xxxByEntries goes to manual;tools.webpack→rspack、webpackChain→bundlerChain、devServer goes to manual (split dev.*). Non-boolean/non-literal values are not silently changed, they go to manual
  • Custom Server: remove the v3-deprecated @modern-js/plugin-server dependency + serverPlugin() call + import;server/index.* when there is unstableMiddleware/afterRender → generate abuildableof server/modern.server.* skeleton (empty defineServerConfig, original logic preserved as comments), Hono Context semantic migration goes into the manual checklist
  • Entry:src/index.*→src/entry.*(bootstrap arrow or function declarationboth forms are rewritten to createRoot()/render(); unrecognized bootstrap forms go to manual, do not pretend success);App.config extract to src/modern.runtime.ts(if it already exists, do not overwrite, goes into the manual checklist)
  • Runtime:useRuntimeContext() → for React 19+ use use(RuntimeContext), for <19 use useContext(RuntimeContext)(keep react default/namespace import;useRuntimeContext as 别名 goes into the manual checklist without fake rewriting)
  • Routing:src/pages→src/routes(when there are no routes), and per the v3 convention map pages/index.* to routes/page.*、pages/foo/index.* map to routes/foo/page.*, synchronously rewrite relative import references, and put any leftovers into the manual checklist; the migration output must not contain routes/**/index.* as a page file;when the root routes/layout.* is missing, automatically generate a minimal Outlet layout(the v3 conventional routing requires a root layout)
  • Scripts: delete the scripts in package.json that call modern new / modern upgrade (v3 has removed these two commands)

applyBaseConfig(...) configuration wrapped by(repository integration test helper / non-standard user configuration):runtime / plugins / dev.port / appTools bundler etc.structural migrations always go into the manual checklist(the report is marked "structural migration incomplete"); only perform file-level safe rewrites such as dependency upgrades / import paths / tailwind removal, and do not semi-automatically break the configuration inside the wrapper.package.json the modernConfig.runtime also goes into the manual checklist.

After completion, view the .agents/runs/modernjs-migrate/report.json of changed / manual. After this step succeeds, execute references/commit-changes.md。

Step 3: Handle each item in the manual checklist one by one (read references as needed)

Based on the report's manual list, read whichever document matches the item hit:

Manual item Reference
App.init / routes/layout the config/init export,modernConfig.runtime, non-empty/functional runtime references/migrate-entry.md
custom Web Server (unstableMiddleware / afterRender) references/migrate-custom-server.md
html.appIcon string,server.ssr.mode, webpack custom configuration,applyBaseConfig(...) structural migration references/migrate-config.md
useRuntimeContext as 别名 call references/migrate-entry.md

After handling each item, execute references/commit-changes.md。

Step 4: Install dependencies

bash scripts/install-deps.sh <projectDir>

After the lock file changes, execute references/commit-changes.md(with --include-lockfiles)。

Step 5: Lint auto-fix

bash scripts/run-lint.sh <projectDir>

After recording a failure, continue; do not treat it as a migration failure.

Step 6: Build verification + final report

Run modern build(key route smoke if necessary). Final report: successful steps, skipped items, failed items, manual handling items, follow-up suggestions.

Safety red lines

  • Prefer structural rewrites; use plain text replacement only for unambiguous items (import paths).
  • Do not manually modify pnpm-lock.yaml / dist / CHANGELOG / node_modules / secret。
  • Complex or uncertain items always go into the manual checklist; do not blindly modify.