Skip to content

feat: app router support via AppPagination named export#140

Open
JoshTheWanderer wants to merge 7 commits into
modernizefrom
app-router-support
Open

feat: app router support via AppPagination named export#140
JoshTheWanderer wants to merge 7 commits into
modernizefrom
app-router-support

Conversation

@JoshTheWanderer
Copy link
Copy Markdown
Member

Summary

  • Refactor into a shared Pagination + routing-adapter pattern; add AppPagination (app router) and PagesPagination (pages router) named exports. Default export stays PagesPagination for backwards compatibility.
  • Postbuild fixes for microbundle-crl: inject 'use client' directive and __esModule marker so the built CJS interop keeps working and app-router consumers don't see RSC boundary errors.
  • Example app gains an /app-example route demonstrating AppPagination. Switch example build to output: 'export' because next export no longer supports the app router.
  • Playwright e2e suite covering both routers, plus a new CI job that builds the lib, installs chromium, and runs the suite; playwright report uploaded on failure.

Test plan

  • npm test (unit + types) green
  • npm run build produces dist/index.js starting with 'use client' and an __esModule marker
  • npm --prefix example run build produces static build/app-example.html
  • npm run test:e2e — 7 tests pass (4 pages, 3 app)
  • CI: e2e job green on this PR

🤖 Generated with Claude Code

JoshTheWanderer and others added 4 commits April 24, 2026 18:57
Extract the presentational component into `src/pagination.tsx` and inject
a routing adapter from per-router wrappers:
- `PagesPagination` (default export) uses `next/router` + `next/head`
- `AppPagination` uses `next/navigation` and omits the `<link rel=prev|next>`
  SEO hints (client components cannot write to `<head>`)

The default export keeps pointing at `PagesPagination` for backwards
compatibility. Both named exports ship from the single entry point.

Postbuild fixes for microbundle-crl:
- Prepend `'use client'` so Next.js treats the bundled module as a client
  component (the directive is stripped during rollup bundling)
- Inject `__esModule` marker so CJS consumers resolve the default export
  via `.default` instead of getting the whole exports object

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an `app/` tree alongside the existing `pages/` directory so the demo
covers both routers:
- `app/layout.jsx` root layout
- `app/app-example/page.jsx` client component rendering `AppPagination`
  inside `<Suspense>` (required for static export with useSearchParams)
- link to `/app-example` from the pages index

Switch the build to `output: 'export'` since `next export` no longer
supports the app router, and rename `out` back to `build` so the
`gh-pages` deploy keeps working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Boots the example dev server via playwright's webServer and exercises the
rendered pagination for the pages router (`/`) and the app router
(`/app-example`): first-page state, clicking a page number updates the
URL and current marker, and the page-size select resets to page 1 with
the new size.

Run locally with `npm run test:e2e`. Chromium only; CI wiring is not
included in this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build the lib, install the example and chromium, then run
`npm run test:e2e`. Upload the playwright report as an artifact so
failures can be inspected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mergify mergify Bot requested review from a team and ella-etch and removed request for a team April 24, 2026 17:32
JoshTheWanderer and others added 3 commits April 27, 2026 11:14
- src/adapters/app.tsx: simplify searchParams dedup with Set.forEach
  instead of an ad-hoc seen map.
- scripts/add-use-client.js: collapse the two passes into a single
  prefix-then-write helper so the directive and __esModule marker can
  no longer interleave incorrectly. The CJS bundle gets both prefixes;
  the ESM bundle only needs the directive.
- playwright + example: run the e2e suite against the production bundle
  (`next build` + `next start`) when CI is set, matching what users
  ship. Locally we keep `next dev` for fast iteration. The deploy build
  now gates `output: 'export'` and basePath/assetPrefix on
  EXAMPLE_DEPLOY=true so the test build uses plain server output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundle split via subpath exports (/app, /pages) so app-router consumers
no longer pull in next/router and pages-router consumers can opt out of
the 'use client' directive. Root entry keeps both adapters for
back-compat.

Replace add-use-client.js with postbuild.js: handles multi-entry rename,
d.ts flatten, stale cleanup, per-entry directive patching. Throws on
missing dist instead of silently no-opping.

Clamp invalid ?page=/?size=/total values silently in the shared core
(parseInt + Number.isFinite + Math.min, handles string[] query values
from the app adapter).

Guard null searchParams/pathname in AppPagination with a dev warning,
replacing the silent ?? '' fallback that masked missing <Suspense>
wrappers.

Force CI to re-pack the example's file:.. dep on every run by switching
from npm ci to npm install --no-package-lock.

Restore unit coverage with src/pagination.test.tsx (core behaviour via
stub adapter) and src/adapters/app.test.tsx (mocked next/navigation).

Document AppPagination, the entrypoint table, the <Suspense>
requirement, and clamping behaviour in the README.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The long-running modernize branch is the base for #140 and future
modernization PRs, so CI needs to fire on PRs into it as well as main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
13.6% Duplication on New Code (required ≤ 10%)

See analysis details on SonarQube Cloud

@github-actions
Copy link
Copy Markdown

MegaLinter analysis: Error

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ ACTION actionlint 1 0 0 0.04s
❌ CSS stylelint 1 1 0 0.0s
✅ EDITORCONFIG editorconfig-checker 29 0 0 0.06s
❌ JAVASCRIPT eslint 4 8 0 2.8s
✅ JSON jsonlint 4 0 0 0.2s
✅ JSON npm-package-json-lint yes no no 0.56s
✅ JSON prettier 4 0 0 1.22s
✅ JSON v8r 4 0 0 7.71s
⚠️ MARKDOWN markdownlint 1 19 0 1.05s
⚠️ MARKDOWN markdown-table-formatter 1 1 0 0.36s
✅ REPOSITORY checkov yes no no 21.44s
⚠️ REPOSITORY gitleaks yes 2 no 135.59s
✅ REPOSITORY git_diff yes no no 0.01s
✅ REPOSITORY secretlint yes no no 1.07s
✅ REPOSITORY syft yes no no 9.34s
✅ REPOSITORY trivy-sbom yes no no 3.64s
❌ REPOSITORY trufflehog yes 1 no 1.69s
❌ TYPESCRIPT eslint 5 7 0 3.55s
⚠️ YAML prettier 1 1 3 0.58s
✅ YAML yamllint 1 0 0 0.79s

Detailed Issues

❌ JAVASCRIPT / eslint - 8 errors
scripts/postbuild.js
    1:12  error  Require statement not part of import statement                                                                                                                      @typescript-eslint/no-var-requires
    2:14  error  Require statement not part of import statement                                                                                                                      @typescript-eslint/no-var-requires
   80:19  error  Insert `⏎·`                                                                                                                                                         prettier/prettier
   86:1   error  Insert `··`                                                                                                                                                         prettier/prettier
   97:17  error  Replace ``postbuild:·${path.relative(process.cwd(),·file)}·already·patched`` with `⏎······`postbuild:·${path.relative(process.cwd(),·file)}·already·patched`⏎····`  prettier/prettier
  115:44  error  Replace `·addDirective:·true,·addEsModule:·false·` with `⏎··addDirective:·true,⏎··addEsModule:·false⏎`                                                              prettier/prettier
  119:42  error  Replace `·addDirective:·true,·addEsModule:·false·` with `⏎··addDirective:·true,⏎··addEsModule:·false⏎`                                                              prettier/prettier
  125:44  error  Replace `·addDirective:·false,·addEsModule:·false·` with `⏎··addDirective:·false,⏎··addEsModule:·false⏎`                                                            prettier/prettier

✖ 8 problems (8 errors, 0 warnings)
  6 errors and 0 warnings potentially fixable with the `--fix` option.
❌ TYPESCRIPT / eslint - 7 errors
src/index.test.ts
  25:32  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any
  28:49  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any

tests/e2e/app-router.spec.ts
  12:75  error  Replace `·page` with `⏎····page⏎·`  prettier/prettier
  21:69  error  Replace `·page` with `⏎····page⏎·`  prettier/prettier

tests/e2e/pages-router.spec.ts
  12:75  error  Replace `·page` with `⏎····page⏎·`                                                                                                                      prettier/prettier
  21:69  error  Replace `·page` with `⏎····page⏎·`                                                                                                                      prettier/prettier
  33:18  error  Replace `page.getByRole('heading',·{·name:·'App·Router·Pagination'·})` with `⏎······page.getByRole('heading',·{·name:·'App·Router·Pagination'·})⏎····`  prettier/prettier

✖ 7 problems (5 errors, 2 warnings)
  5 errors and 0 warnings potentially fixable with the `--fix` option.
❌ CSS / stylelint - 1 error
Fatal error while calling stylelint: [Errno 2] No such file or directory: './node_modules/.bin/stylelint'
❌ REPOSITORY / trufflehog - 1 error
trufflehog: error: flag 'exclude-paths' cannot be repeated, try --help
⚠️ REPOSITORY / gitleaks - 2 errors
○
    │╲
    │ ○
    ○ ░
    ░    gitleaks

Finding:     ...c7d97ac9cc2dc41c4","previewModeSigningKey":"REDACTED","previewModeEncrypt...
Secret:      REDACTED
RuleID:      generic-api-key
Entropy:     3.889819
File:        prerender-manifest.json
Line:        1
Commit:      HIDDEN_BY_MEGALINTERAuthor:      Gavyn McKenzie
Email:       gavyn@etchapps.com
Date:        2020-05-14T14:14:09Z
Fingerprint: 04df8d1917926edb28e87599b3003e401c529959:prerender-manifest.json:generic-api-key:1
Link:        https://github.com/etchteam/next-pagination/blob/04df8d1917926edb28e87599b3003e401c529959/prerender-manifest.json#L1

Finding:     ...74ce1dc9d04ec38c4","previewModeEncryptionKey":"REDACTED"}}
Secret:      REDACTED
RuleID:      generic-api-key
Entropy:     3.858720
File:        prerender-manifest.json
Line:        1
Commit:      HIDDEN_BY_MEGALINTERAuthor:      Gavyn McKenzie
Email:       gavyn@etchapps.com
Date:        2020-05-14T14:14:09Z
Fingerprint: 04df8d1917926edb28e87599b3003e401c529959:prerender-manifest.json:generic-api-key:1
Link:        https://github.com/etchteam/next-pagination/blob/04df8d1917926edb28e87599b3003e401c529959/prerender-manifest.json#L1

10:38AM INF 256 commits scanned.
10:38AM INF scanned ~57745200 bytes (57.75 MB) in 2m16s
10:38AM WRN leaks found: 2
⚠️ MARKDOWN / markdown-table-formatter - 1 error
1 files contain markdown tables to format:
- README.md
⚠️ MARKDOWN / markdownlint - 19 errors
README.md:33:115 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:33:148 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:34:186 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:35:161 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:91:42 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:91:130 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:92:42 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:92:130 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:93:42 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:93:130 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:94:42 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:94:130 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:95:42 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:95:130 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:96:42 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:96:130 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:97:42 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:97:143 error MD060/table-column-style Table column style [Table pipe does not align with header for style "aligned"]
README.md:114 error MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
⚠️ YAML / prettier - 1 error
Checking formatting...
[warn] jsxBracketSameLine is deprecated.
[warn] .github/workflows/ci.yml
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

See detailed reports in MegaLinter artifacts
Set VALIDATE_ALL_CODEBASE: true in mega-linter.yml to validate all sources, not only the diff

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant