Skip to content

feat: add React Router adapter#84

Open
farizanjum wants to merge 9 commits into
dodopayments:mainfrom
farizanjum:feat/react-router-remix-adapter
Open

feat: add React Router adapter#84
farizanjum wants to merge 9 commits into
dodopayments:mainfrom
farizanjum:feat/react-router-remix-adapter

Conversation

@farizanjum

@farizanjum farizanjum commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Closes #6

Summary

  • add @dualmark/remix for React Router v7 Framework Mode
  • generate markdown resource routes and /llms.txt from Dualmark config
  • add entry-server negotiation for bot user agents and Accept: text/markdown
  • add examples/remix-blog plus docs and release metadata

Testing

  • bun run --filter @dualmark/remix test
  • bun run --filter @dualmark/remix typecheck
  • bun run --filter @dualmark/remix build
  • bun run --filter dualmark-example-remix-blog build
  • bun run --filter dualmark-docs build
  • node packages/cli/dist/cli.js verify http://localhost:5174/posts/hello returned 125/125 under react-router dev
  • node packages/cli/dist/cli.js verify http://localhost:3000/posts/hello returned 125/125 under react-router-serve

Notes

  • slugStrategy: "catch-all" is intentionally rejected for now because React Router splats cannot safely express nested *.md routes without catching human HTML paths.
  • The current React Router docs entry point is https://reactrouter.com/start/framework/installation.

Copilot AI review requested due to automatic review settings July 6, 2026 09:22
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

@farizanjum is attempting to deploy a commit to the Dodo Payments Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@farizanjum
farizanjum force-pushed the feat/react-router-remix-adapter branch 2 times, most recently from 9e20a15 to 48f5acf Compare July 12, 2026 18:40

@dodo-squirrels dodo-squirrels Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: feat: add React Router adapter (@dualmark/remix)

Verdict: Approve with follow-ups. Strong, well-structured feature. Two spec-consistency nits below that I'd like to see addressed (ideally before merge, since they re-introduce bugs that sibling PRs #70/#73 are fixing) — neither blocks the core functionality.

What I exercised

Checked out the branch, ran a clean bun install, then:

  • @dualmark/remix: build ✓, typecheck ✓, 24/24 tests ✓
  • examples/remix-blog: builds, generates the 4 markdown resource routes + llms.txt
  • Ran the production server (react-router-serve) and drove it end-to-end with curl
  • Ran the CLI conformance verifier against the live server

Note: on first run test/typecheck failed with Cannot find module @dualmark/core — that was a stale-lockfile linking artifact in my sandbox; a fresh bun install fixed it. Not a PR defect.

Independent conformance result

node packages/cli/dist/cli.js verify http://127.0.0.1:3000/posts/hello
Score: 125/125   ← all 14 checks pass (incl. 406 negotiation, Link alternate, tokens, noindex, nosniff, aeoVersion)

End-to-end header checks all correct: browser → text/html + Link rel=alternate + Vary; GPTBottext/markdown + X-Robots-Tag: noindex + X-Markdown-Tokens; Accept: text/markdown → markdown; direct .md → markdown; /llms.txttext/plain.

Code quality — strong

  • config-validation.ts is thorough: rejects trailing-slash siteUrl, non-relative/.. paths, and correctly rejects Windows absolute paths cross-platform via win32.isAbsolute (the follow-up commit). Route/pattern sanitization is solid.
  • routes.ts GENERATED_MARKER guard refusing to overwrite user-authored route files is a nice safety touch.
  • handlers.ts targeted dispatch + resource-route factoring is clean and reuses core primitives correctly.
  • Intentionally rejecting slugStrategy: "catch-all" with a clear error is the right call for the splat limitation.

Follow-up 1 (spec §5) — bot UA should respect explicit Accept: text/html

entry-server.ts:78 uses the legacy if (bot.isBot || format === "markdown"). I confirmed live: a request with User-Agent: GPTBot/1.0 and Accept: text/html is served text/markdown + noindex on the canonical URL:

curl -H 'User-Agent: GPTBot/1.0' -H 'Accept: text/html' …/posts/hello
→ content-type: text/markdown; x-robots-tag: noindex   ✗ (spec §5: UA MUST NOT override explicit Accept)

PR #70 just introduced shouldServeMarkdown(accept, isBot) in @dualmark/core for exactly this. Please route this adapter through it too.

Follow-up 2 (spec §3) — Vary: Accept gated on injectLinkHeader

entry-server.ts:82-87 only calls appendVaryAccept inside if (injectLinkHeader). With injectLinkHeader: false, negotiable HTML would ship without Vary: Accept — the same cache-poisoning bug PR #73 is fixing across the other adapters. Vary: Accept should be set whenever the page is negotiable, independent of the Link flag.

Minor

  • Package/PR is named "React Router" but publishes as @dualmark/remix — intentional given RRv7 = Remix lineage, just calling it out for discoverability (README/docs do explain it).

Overall this is a high-quality adapter that already hits perfect conformance. Approving; please fold in the two @dualmark/core helpers (shouldServeMarkdown, unconditional Vary) so it lands consistent with #70/#73.

@farizanjum

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. I addressed the Vary follow-up in 3054b11: negotiable HTML now receives Vary: Accept even when injectLinkHeader is false, with a regression test covering Link absent plus Vary present.

For the explicit Accept follow-up, PR #70 is still open and owns the shared shouldServeMarkdown helper. I will rebase onto it once it merges and route @dualmark/remix through that helper rather than duplicate its negotiation logic here.

@farizanjum

farizanjum commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. Both follow-ups are now addressed.

  • Added @dualmark/core shouldServeMarkdown(accept, isBot), consistent with the shared negotiation approach in fix: respect explicit Accept: text/html from AI bot user agents #70.
  • Updated @dualmark/remix to use it, so GPTBot plus Accept: text/html now receives HTML with no noindex header.
  • Kept Vary: Accept independent of injectLinkHeader, with a regression test for Link disabled plus Vary present.
  • Added core and Remix regression coverage.

Verification:

  • bun run test: 26 Turbo tasks successful
  • bun run typecheck: 27 Turbo tasks successful
  • Remix dev and production conformance: 125/125
  • GPTBot plus Accept: text/html: text/html with no X-Robots-Tag

@farizanjum
farizanjum force-pushed the feat/react-router-remix-adapter branch from 3054b11 to 7650514 Compare July 19, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adapter: @dualmark/remix (React Router v7)

2 participants