Migration audit rules (2.x to 3.x)
Audit rules for projects migrating from Front-Commerce 2.x or still carrying 2.x compatibility code.
A 2.x to 3.x migration is a long-running effort during which compatibility layers, temporary degradations, and leftover 2.x code coexist with native 3.x code. These rules verify that this transitional state is controlled: every compatibility mechanism is inventoried, shrinking, and has an exit plan.
This section only applies to projects migrating from Front-Commerce 2.x or still carrying 2.x compatibility code. Skip it entirely for native 3.x projects.
AUDIT-MIG-01 — Keep @front-commerce/compat usage inventoried and decreasing
Severity: important — Detection: static
Rule: Every import from @front-commerce/compat is counted, the count
decreases over time, and a written removal plan assigns each remaining usage to
a ticket or milestone.
Why: The compat package exists to enable a progressive migration, not as a permanent dependency. New Front-Commerce projects must not use it, and features built on compat APIs miss the performance and correctness improvements of the native 3.x equivalents. An uninventoried compat surface means the migration has stalled without anyone deciding it.
How to check:
grep -rn "@front-commerce/compat" app/ extensions/ --include="*.ts" --include="*.tsx" | wc -l
grep -rln "@front-commerce/compat" app/ extensions/ | sort
Record the total and the file list in the audit report and compare with the
previous audit. A violation is: a count that grew since the last measurement,
compat imports in files created after the migration started (new features must
use native APIs), or the absence of any tracked plan for the remaining usages.
If @front-commerce/compat is absent from package.json, this rule and most of
this section pass trivially.
AUDIT-MIG-02 — Leave no v2 module or middleware orphaned
Severity: critical — Detection: static
Rule: Every code directory in the repository is either referenced from
front-commerce.config.ts (directly or through an extension's definition) or
deleted; no v2-era module, Express middleware, or server extension survives
unreferenced.
Why: In 2.x, modules were wired through .front-commerce.js; in 3.x, only
what an extension registers runs. A directory that was never re-wired still sits
in the repository, looks implemented, and silently does nothing. This is a real
incident pattern: a referrer-policy middleware and custom image proxies were
"migrated" as files but never registered — the security header and the proxied
images disappeared from production without any error.
How to check: Build the reference graph from the config entry point:
cat front-commerce.config.ts
grep -rn "defineExtension\|export default" extensions/*/index.ts 2>/dev/null
Then list top-level code directories and match them against the extensions declared in the config:
ls extensions/ src/ 2>/dev/null
grep -rn "\.front-commerce\.js\|onServerInit\|middleware" src/ extensions/ 2>/dev/null | head -20
A violation is any directory containing loaders, middleware, GraphQL modules, or
routes that no declared extension imports — especially a leftover src/ tree
from the v2 layout. For each orphan, decide: re-register it through an
extension, or delete it and record the intentional feature removal. Compare the
v2 module list (from the pre-migration .front-commerce.js, available in git
history) against what the 3.x config registers to catch silent losses.
AUDIT-MIG-03 — Keep a single source of truth for configuration
Severity: important — Detection: static
Rule: Each configuration concern (stores, website, caching, analytics,
cookies services…) is defined in exactly one place — the 3.x app/config/
directory — and the v2 src/config/ tree contains no live duplicate.
Why: During migration, config files get copied from src/config/ to
app/config/ and the original is often kept "for reference". Two files then
drift: a fix applied to the dead copy has no effect, and a developer reading the
wrong one reasons from stale values. Real projects have shipped incidents caused
by editing the unused copy.
How to check:
ls src/config/ 2>/dev/null && ls app/config/
for f in $(ls src/config/ 2>/dev/null); do [ -e "app/config/$f" ] && echo "DUPLICATE: $f"; done
grep -rn "src/config" app/ extensions/ front-commerce.config.ts 2>/dev/null
A violation is any file present under both trees, or any import still pointing
into src/config/ while an app/config/ twin exists. The fix is mechanical:
confirm which copy is actually imported, merge any drift into it, and delete the
other. If src/config/ is entirely unreferenced, delete the whole directory.
AUDIT-MIG-04 — Remove patches targeting the previous major version
Severity: important — Detection: static
Rule: Every patch file (patch-package, pnpm patch) applies to a
dependency version actually installed, and no patch targets a package or
toolchain that only existed in the 2.x stack (webpack, 2.x Front-Commerce
internals).
Why: A patch written for the v2 toolchain either fails the install or — worse — applies to nothing and lulls the team into believing the fix is still active. A real project carried a committed webpack patch on a v3/Vite codebase: the patched behavior was silently gone, and the patch directory suggested otherwise to every reader.
How to check:
ls patches/ 2>/dev/null
grep -n "patchedDependencies" package.json pnpm-workspace.yaml 2>/dev/null
For each patch, compare the package name and version in the patch filename (for
example webpack+4.46.0.patch) against the installed version:
pnpm why <package> 2>/dev/null || echo "NOT INSTALLED"
A violation is a patch whose target package is absent from the lockfile, whose version no longer matches, or that patches a 2.x-only tool (webpack, babel plugins) in a Vite-based project. Delete stale patches; for still-needed ones, record why upstream cannot ship the fix.
AUDIT-MIG-05 — Scope the v2 catch-all route to a known, shrinking page list
Severity: important — Detection: static
Rule: If the project still uses the v2 compatibility catch-all route
(_main.$.tsx delegating to compat LoadableRoutes), the list of pages it
serves is explicit, tracked, and shrinking; every newly migrated page gets a
native Remix route and is removed from the catch-all's scope.
Why: The catch-all renders v2-style routes through the compat layer, without
the benefits of native Remix routes: loader-based data fetching, route-level
meta, granular error boundaries, and shouldRevalidate control. When its
scope is unknown, pages believed migrated may still be served by the compat
path, and the migration cannot be declared done.
How to check:
ls app/routes/ | sort
cat "app/routes/_main.\$.tsx" 2>/dev/null
grep -rn "LoadableRoutes\|web/moduleRoutes" app/ src/ --include="*.tsx" --include="*.js" 2>/dev/null
A violation is a catch-all present with no accompanying inventory of which URLs
it still serves, or a v2 route file (in the compat routes tree) whose URL also
has a native app/routes/ file — the native one shadows it, leaving dead code.
Cross-check the inventory against the sitemap or the route list from the v2
project. When the catch-all serves zero pages, delete it along with the compat
route definitions.
AUDIT-MIG-06 — Document and ticket every temporary degradation
Severity: important — Detection: manual
Rule: Every capability deliberately degraded during the migration (Apollo SSR disabled, link preloading disabled, a feature stubbed out) is written down with the date it was introduced, the user-visible impact, and a ticket for its resolution.
Why: A degradation accepted "for two weeks" without a ticket becomes the permanent state of production. SSR disabled for client-side Apollo queries, for example, means SEO-critical content is invisible to crawlers — acceptable as a conscious step, harmful as a forgotten default.
How to check: Gather the candidates, then require evidence for each:
grep -rn "v2_compat" front-commerce.config.ts
grep -rn -i "TODO\|FIXME\|temporar\|disabled" front-commerce.config.ts app/config/ | head -20
For each flag or disabling found (useApolloClientSSR: false while the app
still runs SEO-critical Apollo queries, preload options turned off, commented
out features), ask for the ticket reference and the documented impact. A
violation is any degradation that no document or ticket accounts for — the
judgment of what counts as "degraded" versus "intended" is why this rule is
manual. Record each undocumented degradation as its own finding with an owner.
AUDIT-MIG-07 — Delete one-shot migration scripts after execution
Severity: minor — Detection: static
Rule: Codemod outputs, data-fixing scripts, and other run-once migration tooling are removed from the repository once executed; anything kept is moved out of the build path and documented.
Why: A migration script that already ran is dead code with sharp edges:
rerunning it can corrupt data, and its presence in scripts/ or bin/ suggests
it is part of the normal workflow. It also drags obsolete dependencies into the
lockfile.
How to check:
ls scripts/ bin/ tools/ 2>/dev/null
grep -n "migrate\|codemod" package.json
grep -rn "TODO (Codemod generated)" app/ extensions/ src/ 2>/dev/null | wc -l
A violation is a committed one-shot script with no README explaining why it is
kept, a package.json script entry invoking a migration that already ran, or
remaining TODO (Codemod generated) comments — those mark manual migration
steps the codemods could not complete, and each one is unfinished migration
work, not a regular TODO.
AUDIT-MIG-08 — Inventory v2 library shims with a removal plan
Severity: important — Detection: static
Rule: Every shim, alias, or stub kept for a 2.x-era library (react-router v5
APIs, react-helmet-async, analytics.js-style wrappers) is listed with the
components depending on it and a planned removal milestone.
Why: 3.x replaces these libraries at the framework level: routing comes from
@remix-run/react (react-router v6 underneath) and head management from route
meta functions — react-helmet-async was removed from the framework. Shims
keep old call sites compiling, but they freeze the project on unmaintained API
surfaces and can conflict with the framework's own behavior (two head managers
fighting over tags, two router contexts).
How to check:
grep -rn "react-helmet\|react-router-dom" app/ extensions/ src/ package.json --include="*.ts" --include="*.tsx" --include="*.json" 2>/dev/null
grep -n "alias" vite.config.ts front-commerce.config.ts 2>/dev/null
A violation is: react-router-dom or react-helmet-async still in
package.json, a Vite alias mapping a v2 library to a local stub without an
inventory of its consumers, or components still using <Helmet> instead of
route meta exports for SEO-relevant tags (title, description, canonical) —
head tags rendered client-side only are invisible to crawlers that do not
execute JavaScript. For each shim, the report lists its dependents (the grep
output above) and the milestone where it disappears.
AUDIT-MIG-09 — Keep v2_compat flags minimal and justified
Severity: important — Detection: static
Rule: Every flag enabled under v2_compat in front-commerce.config.ts
corresponds to a pattern the codebase actually still uses, and no flag remains
enabled after its last consumer was migrated.
Why: The v2_compat flags (useApolloClientQueries, useApolloClientSSR,
…) exist to keep deprecated patterns working during the transition, at a cost:
useApolloClientSSR for instance adds an SSR rendering pass for Apollo queries.
A flag left enabled after the code it served is gone imposes that cost for
nothing and hides the fact that the migration step is actually complete.
How to check: Cross-check each enabled flag against its consumers:
grep -n -A10 "v2_compat" front-commerce.config.ts
grep -rn "graphql(" app/ extensions/ --include="*.tsx" | grep -v "\.graphql\|gql\`" | head
grep -rn "useQuery\|@apollo/client" app/ extensions/ --include="*.tsx" | head
A violation is: useApolloClientQueries enabled while no component uses the
Apollo graphql() HOC or Apollo client-side queries anymore, or
useApolloClientSSR enabled while no remaining Apollo query renders
SEO-critical content. Conversely, Apollo usages present with the matching flag
disabled indicate a latent hydration bug. For each enabled flag, the report
names at least one remaining consumer — a flag with zero consumers must be
turned off in the same audit cycle.