Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ jobs:
kill "$SERVER_PID" 2>/dev/null || true
exit 1

shim-coverage:
name: Shim Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-pnpm
- run: pnpm dlx tsx scripts/check-shim-coverage.ts

e2e:
name: E2E (${{ matrix.project }})
runs-on: ubuntu-latest
Expand Down
108 changes: 108 additions & 0 deletions .github/workflows/nextjs-api-track.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: "Next.js API Tracking"

on:
schedule:
- cron: "0 8 * * 1" # Monday 8am UTC
workflow_dispatch: # manual trigger

permissions:
contents: read
issues: write

concurrency:
group: nextjs-api-track
cancel-in-progress: true

jobs:
track:
name: Check for API changes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-pnpm

- name: Get latest stable Next.js version
id: next-version
run: |
LATEST=$(npm view next version)
CURRENT=$(node -e "console.log(JSON.parse(require('fs').readFileSync('api-manifest.json','utf-8')).version)")
echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
if [ "$LATEST" = "$CURRENT" ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Extract new API manifest
if: steps.next-version.outputs.changed == 'true'
run: |
TMP_DIR=$(mktemp -d)
cd "$TMP_DIR"
TARBALL=$(npm pack "next@${{ steps.next-version.outputs.latest }}" --silent)
tar -xzf "$TARBALL"
pnpm dlx tsx "$GITHUB_WORKSPACE/scripts/extract-nextjs-api.ts" "$TMP_DIR/package" "$GITHUB_WORKSPACE/new-manifest.json"

- name: Diff manifests
if: steps.next-version.outputs.changed == 'true'
id: diff
run: |
DIFF=$(pnpm dlx tsx scripts/diff-nextjs-api.ts api-manifest.json new-manifest.json 2>&1) || true
echo "diff<<EOF" >> "$GITHUB_OUTPUT"
echo "$DIFF" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"

- name: Ensure tracking label exists
if: steps.next-version.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh label create "next-api-tracking" --description "Automated Next.js API surface tracking" --color "0e8a16" 2>/dev/null || true

- name: Open tracking issue
if: steps.next-version.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEXT_LATEST: ${{ steps.next-version.outputs.latest }}
NEXT_CURRENT: ${{ steps.next-version.outputs.current }}
API_DIFF: ${{ steps.diff.outputs.diff }}
REPO: ${{ github.repository }}
run: |
MARKER="<!-- next-api-track: next@${NEXT_LATEST} -->"
EXISTING=$(gh issue list --state open --search "\"next-api-track: next@${NEXT_LATEST}\" in:body" --json number --jq '.[0].number // empty')
if [ -n "$EXISTING" ]; then
echo "Issue #$EXISTING already exists for next@$NEXT_LATEST, skipping"
exit 0
fi

read -r -d '' ISSUE_BODY <<BODY || true
## Next.js API Surface Change Detected

**Previous version:** ${NEXT_CURRENT}
**New version:** ${NEXT_LATEST}

${MARKER}

### API Diff

\`\`\`
${API_DIFF}
\`\`\`

### Action Items

- [ ] Review API changes
- [ ] Update shims for new exports
- [ ] Update \`known-gaps.json\` for intentionally unsupported features
- [ ] Regenerate \`api-manifest.json\` with \`pnpm dlx tsx scripts/extract-nextjs-api.ts\`
- [ ] Run \`pnpm dlx tsx scripts/check-shim-coverage.ts\` to verify coverage

_This issue was automatically created by the [Next.js API Tracking workflow](https://github.com/${REPO}/actions/workflows/nextjs-api-track.yml)._
BODY

# Strip leading whitespace (10 spaces from YAML indentation)
ISSUE_BODY=$(echo "$ISSUE_BODY" | sed 's/^ //')

gh issue create \
--title "Next.js API update: ${NEXT_CURRENT} → ${NEXT_LATEST}" \
--label "next-api-tracking" \
--body "$ISSUE_BODY"
20 changes: 20 additions & 0 deletions .github/workflows/tip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ name: "Next.js tip"

on:
workflow_dispatch: # disabled schedule — re-enable once ecosystem tests are stable
inputs:
run_compat_tests:
description: "Run vinext compatibility tests"
type: boolean
default: true

permissions:
contents: read
Expand Down Expand Up @@ -32,6 +37,21 @@ jobs:
repo_url: ${{ matrix.repo.url }}
next_version: "canary"

compat-tests:
name: Compat Tests
if: inputs.run_compat_tests != false
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-node-pnpm
- name: Install next@canary
run: pnpm add -D next@canary --filter vinext
- name: Build plugin
run: pnpm run build
- name: Run compat tests
run: pnpm test tests/nextjs-compat/

notify-on-failure:
name: Send failure email
needs: test
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
node_modules/
dist/
!tests/fixtures/next-api-manifest/**/dist/
!tests/fixtures/next-api-manifest/**/dist/**
out/
*.tsbuildinfo
.vite/
Expand Down
152 changes: 152 additions & 0 deletions api-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{
"version": "16.1.6",
"extractedAt": "2026-03-11T07:56:13.539Z",
"modules": {
"next/app": ["default"],
"next/babel": ["default"],
"next/cache": [
"cacheLife",
"cacheTag",
"refresh",
"revalidatePath",
"revalidateTag",
"unstable_cache",
"unstable_cacheLife",
"unstable_cacheTag",
"unstable_noStore",
"updateTag"
],
"next/client": ["emitter", "hydrate", "initialize", "router", "version"],
"next/compat/router": ["useRouter"],
"next/constants": [
"APP_CLIENT_INTERNALS",
"APP_PATHS_MANIFEST",
"APP_PATH_ROUTES_MANIFEST",
"AdapterOutputType",
"BARREL_OPTIMIZATION_PREFIX",
"BLOCKED_PAGES",
"BUILD_ID_FILE",
"BUILD_MANIFEST",
"CLIENT_PUBLIC_FILES_PATH",
"CLIENT_REFERENCE_MANIFEST",
"CLIENT_STATIC_FILES_PATH",
"CLIENT_STATIC_FILES_RUNTIME_MAIN",
"CLIENT_STATIC_FILES_RUNTIME_MAIN_APP",
"CLIENT_STATIC_FILES_RUNTIME_POLYFILLS",
"CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL",
"CLIENT_STATIC_FILES_RUNTIME_REACT_REFRESH",
"CLIENT_STATIC_FILES_RUNTIME_WEBPACK",
"COMPILER_INDEXES",
"COMPILER_NAMES",
"CONFIG_FILES",
"DEFAULT_RUNTIME_WEBPACK",
"DEFAULT_SANS_SERIF_FONT",
"DEFAULT_SERIF_FONT",
"DEV_CLIENT_MIDDLEWARE_MANIFEST",
"DEV_CLIENT_PAGES_MANIFEST",
"DYNAMIC_CSS_MANIFEST",
"EDGE_RUNTIME_WEBPACK",
"EDGE_UNSUPPORTED_NODE_APIS",
"EXPORT_DETAIL",
"EXPORT_MARKER",
"FUNCTIONS_CONFIG_MANIFEST",
"IMAGES_MANIFEST",
"INTERCEPTION_ROUTE_REWRITE_MANIFEST",
"MIDDLEWARE_BUILD_MANIFEST",
"MIDDLEWARE_MANIFEST",
"MIDDLEWARE_REACT_LOADABLE_MANIFEST",
"MODERN_BROWSERSLIST_TARGET",
"NEXT_BUILTIN_DOCUMENT",
"NEXT_FONT_MANIFEST",
"PAGES_MANIFEST",
"PHASE_ANALYZE",
"PHASE_DEVELOPMENT_SERVER",
"PHASE_EXPORT",
"PHASE_INFO",
"PHASE_PRODUCTION_BUILD",
"PHASE_PRODUCTION_SERVER",
"PHASE_TEST",
"PRERENDER_MANIFEST",
"REACT_LOADABLE_MANIFEST",
"ROUTES_MANIFEST",
"RSC_MODULE_TYPES",
"SERVER_DIRECTORY",
"SERVER_FILES_MANIFEST",
"SERVER_PROPS_ID",
"SERVER_REFERENCE_MANIFEST",
"STATIC_PROPS_ID",
"STATIC_STATUS_PAGES",
"STRING_LITERAL_DROP_BUNDLE",
"SUBRESOURCE_INTEGRITY_MANIFEST",
"SYSTEM_ENTRYPOINTS",
"TRACE_OUTPUT_VERSION",
"TURBOPACK_CLIENT_BUILD_MANIFEST",
"TURBOPACK_CLIENT_MIDDLEWARE_MANIFEST",
"TURBO_TRACE_DEFAULT_MEMORY_LIMIT",
"UNDERSCORE_GLOBAL_ERROR_ROUTE",
"UNDERSCORE_GLOBAL_ERROR_ROUTE_ENTRY",
"UNDERSCORE_NOT_FOUND_ROUTE",
"UNDERSCORE_NOT_FOUND_ROUTE_ENTRY",
"WEBPACK_STATS"
],
"next/document": ["Head", "Html", "Main", "NextScript", "default"],
"next/dynamic": ["default", "noSSR"],
"next/error": ["default"],
"next/experimental/testing/server": ["getRedirectUrl", "getRewrittenUrl", "isRewrite"],
"next/experimental/testmode/playwright": [
"default",
"defaultPlaywrightConfig",
"defineConfig",
"test"
],
"next/experimental/testmode/playwright/msw": ["default", "defineConfig", "test"],
"next/experimental/testmode/proxy": ["createProxyServer"],
"next/form": ["default"],
"next/head": ["default", "defaultHead"],
"next/headers": ["cookies", "draftMode", "headers"],
"next/image": ["default", "getImageProps"],
"next/jest": ["default"],
"next/legacy/image": ["default"],
"next/link": ["default", "useLinkStatus"],
"next/navigation": [
"ReadonlyURLSearchParams",
"RedirectType",
"ServerInsertedHTMLContext",
"forbidden",
"notFound",
"permanentRedirect",
"redirect",
"unauthorized",
"unstable_isUnrecognizedActionError",
"unstable_rethrow",
"useParams",
"usePathname",
"useRouter",
"useSearchParams",
"useSelectedLayoutSegment",
"useSelectedLayoutSegments",
"useServerInsertedHTML"
],
"next/og": ["ImageResponse"],
"next/router": [
"Router",
"createRouter",
"default",
"makePublicRouterInstance",
"useRouter",
"withRouter"
],
"next/script": ["default", "handleClientScriptLoad", "initScriptLoader"],
"next/server": [
"ImageResponse",
"NextRequest",
"NextResponse",
"URLPattern",
"after",
"connection",
"userAgent",
"userAgentFromString"
],
"next/web-vitals": ["useReportWebVitals"]
}
}
72 changes: 72 additions & 0 deletions known-gaps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"next/jest": {
"exports": ["*"],
"status": "wont-fix",
"reason": "vinext uses Vitest, not Jest"
},
"next/babel": {
"exports": ["*"],
"status": "wont-fix",
"reason": "vinext uses Vite and does not provide Next.js's Babel preset entrypoint"
},
"next/client": {
"exports": ["*"],
"status": "wont-fix",
"reason": "Internal Next.js client module (emitter, hydrate, initialize, router, version), not a public API"
},
"next/app": {
"exports": ["*"],
"status": "wont-fix",
"reason": "next/app is a Pages Router entrypoint that vinext does not emulate as a standalone runtime module"
},
"next/experimental/testing/server": {
"exports": ["*"],
"status": "wont-fix",
"reason": "Experimental testing helpers are outside vinext's compatibility target"
},
"next/experimental/testmode/playwright": {
"exports": ["*"],
"status": "wont-fix",
"reason": "Experimental Playwright helpers are outside vinext's compatibility target"
},
"next/experimental/testmode/playwright/msw": {
"exports": ["*"],
"status": "wont-fix",
"reason": "Experimental Playwright/MSW helpers are outside vinext's compatibility target"
},
"next/experimental/testmode/proxy": {
"exports": ["*"],
"status": "wont-fix",
"reason": "Experimental testmode proxy helpers are outside vinext's compatibility target"
},
"next/navigation": {
"exports": ["unstable_rethrow", "unstable_isUnrecognizedActionError"],
"status": "planned",
"reason": "Error rethrowing and action error detection not yet implemented"
},
"next/router": {
"exports": ["Router", "createRouter", "makePublicRouterInstance", "withRouter"],
"status": "planned",
"reason": "Router named export and legacy utilities (createRouter, makePublicRouterInstance, withRouter) not yet exported"
},
"next/server": {
"exports": ["ImageResponse"],
"status": "planned",
"reason": "ImageResponse is in next/og shim but not re-exported from next/server yet"
},
"next/head": {
"exports": ["defaultHead"],
"status": "planned",
"reason": "defaultHead utility not yet exported"
},
"next/dynamic": {
"exports": ["noSSR"],
"status": "planned",
"reason": "noSSR standalone function not yet exported (ssr:false option works via dynamic())"
},
"next/cache": {
"exports": ["unstable_cacheLife", "unstable_cacheTag"],
"status": "planned",
"reason": "Deprecated aliases for cacheLife/cacheTag not yet exported"
}
}
Loading
Loading