nextjs-pages-to-app-router-mining
A read-only mining codemod that scans a Next.js codebase and produces a
semantic inventory of everything relevant to a Pages Router → App Router
migration. It never edits your code — it emits codemod:metrics
you can use to scope, prioritize, and track the migration route-by-route.
See CASES.md for the full design rationale behind each probe.
What it collects
Each metric is grouped by cardinality dimensions so you can slice the results
(e.g. all router.query usages, or every file bucketed as manual).
| Metric | Dimensions | Captures |
|---|---|---|
nextjs-file-kind | kind | page / api-route / app-entry / document (from path) |
nextjs-data-fetching | fn, wrapped | getServerSideProps / getStaticProps / getStaticPaths / getInitialProps; wrapped=true for wrapper calls, reexport for export { x as getServerSideProps } |
nextjs-data-context | fn, prop | context props read (params, query, req, res, preview, locale, …) |
nextjs-data-return | fn, shape | return shapes (props, redirect, notFound, revalidate, fallback, paths) |
nextjs-router-import | name | default (Router) / useRouter / withRouter / NextRouter from next/router |
nextjs-router-usage | member | member accessed on a resolved router instance (push, query, pathname, asPath, …) |
nextjs-router-risk | pattern | higher-risk usage: push-object, shallow-routing, events, beforePopState |
nextjs-client-reason | category, name | why a file needs "use client": hook / global / jsx-handler / context / class |
nextjs-head | kind | next/head contents: import / static-title / static-meta / dynamic / script / other |
nextjs-pages-type | name | Pages-specific type imports (NextPage, GetServerSideProps, AppProps, …) |
nextjs-api-type | name | NextApiRequest / NextApiResponse / NextApiHandler |
nextjs-api-method | method | HTTP method dispatch (GET, POST, …) from req.method checks / switch |
nextjs-api-req | member | request members used (query, body, cookies, headers, socket) |
nextjs-api-res | member | response members used (status, json, send, end, redirect, write, …) |
nextjs-layout | pattern | getLayout / layout / Layout per-page layouts; app-component (<Component {...pageProps} />) |
nextjs-test-mock | target | jest.mock / vi.mock of next/router, next/head, next/navigation |
nextjs-migration-bucket | file, bucket | per-file difficulty: deterministic / ast-assisted / manual |
The nextjs-migration-bucket metric is the headline: it classifies each
Next-related file into a migration difficulty bucket. manual is set when a
file uses getInitialProps, a wrapped data function, risky router APIs
(events / beforePopState / object push / shallow routing), streaming API
responses (res.write / res.revalidate), or <script> inside <Head>.
Targets
tsx grammar over **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}, excluding
node_modules, dist, and build.
Usage
bash
Because the codemod returns null for every file, no source files are
modified — it is safe to run on any branch.
Precision notes
- Router usage is binding-aware. It resolves the local name of
useRouter
(including aliases) andconst router = useRouter(), plus the default
Routerimport, and only counts members read from those bindings. - API req/res mining is gated.
req/resmembers are only counted on files
that are genuine API routes (underpages/api, or importing aNextApi*
type), so a page'sgetServerSidePropsreadingreq.cookiesis not
misattributed as an API route (it is captured bynextjs-data-context). - Path-based signals (
nextjs-file-kind, thepages/apigate) rely on the
file path relative to the run target; content-based signals do not. - Browser-global detection keys on member access (
window.x,localStorage.y)
andmatchMedia(...); baretypeof windowguards are intentionally ignored to
avoid noise.
Development
bash
Each test fixture under tests/ contains input.tsx, an identical
expected.tsx (this codemod never edits code), and a metrics.json snapshot of
the metrics that fixture should produce.
License
MIT