Skip to content

Track stable Next.js API surface with a checked-in manifest and CI coverage gate #454

@Divkix

Description

@Divkix

Summary

vinext already has strong compatibility infrastructure:

  • upstream-ported tests in tests/nextjs-compat/TRACKING.md
  • a migration/compatibility scanner in packages/vinext/src/check.ts
  • CI coverage in .github/workflows/ci.yml
  • daily ecosystem validation in .github/workflows/ecosystem.yml
  • advisory canary testing in .github/workflows/tip.yml

What we do not have yet is a machine-readable compatibility model for stable Next.js releases.

Today, compatibility knowledge is spread across:

  • the inline shim alias map in packages/vinext/src/index.ts
  • shim source files under packages/vinext/src/shims/
  • user-facing support maps in packages/vinext/src/check.ts
  • ported Next.js tests in tests/nextjs-compat/

That works, but it means we do not have a single CI-enforced answer to:

  • what is the stable public next/* API surface for version x.y.z?
  • which exports are implemented by vinext?
  • which missing exports are intentional vs accidental?
  • when did stable Next.js add or remove something we should track?

This issue proposes a stable-only compatibility tracking system built around a checked-in manifest extracted from the published next npm package.

Goals

  • Track stable Next.js public runtime API surface from the published package.
  • Check in a canonical api-manifest.json for the currently supported stable version.
  • Enforce shim coverage in CI.
  • Document intentional gaps explicitly.
  • Add a small behavioral contract suite for high-risk parity cases.
  • Keep canary as advisory signal only.

Non-Goals

  • Do not make canary a required merge gate.
  • Do not use check.ts as the canonical compatibility model.
  • Do not block this work on type-export tracking in v1.
  • Do not block this work on a broad ecosystem internal-import scanner.
  • Do not rewrite the existing Next.js compat suite.

Decisions

  • v1 tracks runtime exports only.
  • Weekly stable tracking opens an issue, not an auto-PR.
  • Ecosystem workflows should pin to the exact manifest version, not a floating minor like "16.1".
  • check.ts remains a user-facing migration tool, not the internal engineering source of truth.

Current vs Proposed

Area Current Proposed
Source of truth Inline shim map in index.ts + hand-maintained support maps in check.ts Checked-in api-manifest.json + exported shim registry + known-gaps.json
Stable release tracking Manual / ad hoc Automated stable-only diff workflow
PR protection Existing CI/E2E/ecosystem checks Existing checks + required shim coverage gate
Behavioral drift detection Ported tests and ecosystem builds Existing tests + small dual-target contracts
Canary Advisory only Still advisory only

Architecture

Current:

             Next.js stable changes
                    |
              humans notice
                    |
   +----------------+----------------+
   |                                 |
ported tests                    manual shim updates
(TRACKING.md)                   (index.ts + shims)
   |                                 |
   +----------------+----------------+
                    |
                   CI

Proposed:

          published next@stable release
                    |
         extract-nextjs-api script
                    |
             api-manifest.json
                    |
   +----------------+----------------+
   |                                 |
weekly diff workflow         PR coverage gate
(open issue)                 (fail on missing exports
                             unless in known-gaps.json)
   |                                 |
   +----------------+----------------+
                    |
        existing CI + contract tests

Registry refactor:

TODAY
index.ts
  - owns inline nextShimMap
  - resolve.alias uses it
  - resolveId uses it

PROPOSED
shims/registry.ts
  - public shim entries
  - internal shim entries
  - shared helper(s)

index.ts
  - imports registry

coverage script
  - imports same registry

Proposed Work

1. Extract a stable public API manifest

Add:

  • scripts/extract-nextjs-api.mjs
  • api-manifest.json

Behavior:

  • download or npm pack the published stable next@x.y.z
  • inspect public next/* entrypoints from the published package
  • record runtime exports only in v1
  • store version metadata in the manifest

Example shape:

{
  "version": "16.1.6",
  "modules": {
    "next/cache": {
      "runtime": [
        "unstable_cache",
        "revalidatePath",
        "revalidateTag",
        "updateTag",
        "refresh"
      ]
    }
  }
}

Notes:

  • Prefer the published npm package over repo source as the source of truth.
  • Runtime-only keeps v1 simpler and covers what actually breaks apps.
  • Wrapper modules are mostly explicit; next/navigation may require a one-hop resolution into compiled output.

2. Move the shim registry into a shared module

Add:

  • packages/vinext/src/shims/registry.ts

Update:

  • packages/vinext/src/index.ts

Behavior:

  • move the inline nextShimMap into a reusable exported registry
  • keep public next/* entries separate from internal next/dist/* aliases
  • make both the plugin and the coverage script consume the same registry

This avoids parsing index.ts as source text in CI.

3. Add a required shim coverage check

Add:

  • scripts/check-shim-coverage.mjs
  • known-gaps.json

Update:

  • .github/workflows/ci.yml

Behavior:

  • read api-manifest.json
  • read the shared shim registry
  • inspect actual named/default exports from shim files
  • fail if a stable public export is missing unless explicitly listed in known-gaps.json
  • fail if a manifest module has no mapped shim and no documented gap

Suggested known-gaps.json shape:

{
  "next/jest": {
    "exports": ["*"],
    "status": "wont-fix",
    "reason": "vinext uses Vitest, not Jest"
  },
  "next/amp": {
    "exports": ["useAmp"],
    "status": "stub",
    "reason": "Deprecated API; shimmed behavior is intentionally minimal"
  }
}

Suggested statuses:

  • planned
  • stub
  • wont-fix

This lets reviewers distinguish intentional non-goals from temporary gaps.

4. Add stable-only tracking workflow

Add:

  • .github/workflows/nextjs-api-track.yml
  • optionally scripts/diff-nextjs-api.mjs

Behavior:

  • run weekly
  • check npm view next version
  • compare against api-manifest.json
  • if unchanged, exit
  • if changed, extract new manifest and open one tracking issue with the diff

This should target stable releases only.

5. Add a small dual-target contract suite

Add:

  • tests/contracts/
  • tests/fixtures/contract-app/

Initial contracts:

  • redirect() status behavior
  • cookies() mutability gating
  • headers() read-only behavior
  • middleware request header propagation
  • generateMetadata() merge chain

Behavior:

  • same fixture app should run under vinext and real stable Next.js
  • use as signal, not a required PR gate in v1
  • run on schedule or workflow dispatch against stable Next.js

6. Keep canary advisory-only

Update:

  • .github/workflows/tip.yml

Behavior:

  • keep next@canary as non-blocking early warning
  • optionally run a focused compat subset
  • do not make canary failures block PRs or releases

Testing / Validation

This work needs tests at three layers: script correctness, shim coverage enforcement, and behavioral parity.

1. Script-level tests

Add Vitest coverage for the new scripts and helpers.

Suggested files:

  • tests/api-manifest.test.ts
  • tests/shim-coverage.test.ts

These should cover:

  • extracting runtime exports from explicit wrapper modules
  • handling module.exports = require(...) style wrappers
  • handling the one-hop next/navigation style case
  • diffing old vs new manifests
  • failing when a manifest export has no shim and no known gap
  • passing when a missing export is explicitly listed in known-gaps.json

Prefer fixture-based tests over hitting the network.

Suggested fixtures:

  • tests/fixtures/next-api-manifest/

2. Registry refactor safety

The registry extraction from index.ts must not change runtime behavior.

Validation:

  • existing shim tests continue to pass
  • existing compat tests continue to pass
  • resolve.alias and resolveId still handle:
    • next/*
    • next/*.js
    • internal next/dist/* aliases

Minimum targeted test runs:

  • pnpm test tests/shims.test.ts
  • pnpm test tests/nextjs-compat/

3. Contract tests

Add a small dual-target suite for behavioral parity.

Suggested files:

  • tests/contracts/redirect.contract.test.ts
  • tests/contracts/request-apis.contract.test.ts
  • tests/contracts/middleware.contract.test.ts
  • tests/contracts/metadata.contract.test.ts

Initial cases:

  • redirect() status behavior
  • cookies() mutability gating
  • headers() read-only behavior
  • middleware request header propagation
  • generateMetadata() merge chain

These should run against the same fixture app under:

  • vinext
  • real stable Next.js

4. CI / workflow validation

Required on PRs:

  • shim coverage check against api-manifest.json

Non-blocking / scheduled:

  • weekly stable release manifest diff
  • advisory canary signal in tip.yml
  • dual-target contract run against stable Next.js

5. Definition of done

This issue is complete when:

  • api-manifest.json is checked in and versioned
  • known-gaps.json is checked in and required for intentional omissions
  • the shim registry no longer lives only as inline state in index.ts
  • CI fails if a stable public next/* export is missing without a documented gap
  • new scripts have direct Vitest coverage
  • the registry refactor does not regress existing shim/compat tests
  • at least 3-5 dual-target contract tests are in place
  • a weekly stable-only tracking workflow exists
  • tip.yml remains advisory only

Follow-ups

Out of scope for v1, but natural follow-ups:

  • type-export tracking
  • ecosystem internal-import scanner for next/dist/*
  • auto-PR generation instead of issue-only tracking
  • tighter coupling between manifest version and ecosystem workflow version pinning

Why this is worth doing

This does not replace the current test suite or ecosystem jobs. It fills a different gap: machine-enforced API drift detection for stable releases.

That gives us:

  • a canonical compatibility model
  • fewer silent regressions
  • explicit documentation of intentional gaps
  • a cleaner path to tracking new stable Next.js releases

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions