Skip to content

Conversation

@wobsoriano
Copy link
Member

@wobsoriano wobsoriano commented Feb 6, 2026

Description

Brings keyless mode support to @clerk/react-router, removing the need for API keys during development setup.

Changes

  • Added a shared keyless resolution logic from framework packages (resolveKeysWithKeylessFallback.ts). SDKs will now write a 10-line wrapper to resolve Clerk keys, falling back to keyless mode in development if configured keys are missing.
  • Extracted reusable test utilities for keyless testing (keylessHelpers.ts)

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Keyless quickstart for React Router: dev-only flow to claim apps and obtain keys, with improved server-side support and environment-aware toggle to enable keyless mode.
    • File-based key storage and runtime checks for non-Node environments; keyless URLs now flow through client/server state for UI and redirects.
  • Tests

    • Added and consolidated Playwright integration tests and shared helpers covering keyless UI, claiming flow, key copying, and dev-server behaviors.

@vercel
Copy link

vercel bot commented Feb 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Feb 10, 2026 3:01am

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 6, 2026

🦋 Changeset detected

Latest commit: cda1649

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/react-router Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 6, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7794

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7794

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7794

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7794

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7794

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7794

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7794

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7794

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7794

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7794

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7794

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7794

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7794

@clerk/react

npm i https://pkg.pr.new/@clerk/react@7794

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7794

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7794

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7794

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7794

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7794

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7794

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7794

commit: cda1649

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

Adds keyless support to the React Router package: server-side keyless service with file-backed storage and lazy singleton init; shared resolveKeysWithKeylessFallback helper and KeylessResult type; middleware and request-state propagation of keyless URLs; client provider propagation of internal keyless URLs; feature flag canUseKeyless and relaxed loadOptions validation to allow keyless in development; new file storage factory, utilities, and Playwright integration tests plus shared test helpers for keyless flows.

Possibly related PRs

  • A parallel change implements equivalent keyless plumbing (service initialization, file storage, key resolution, feature flags, and tests) for the TanStack Start framework in the companion frontend repository.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding keyless mode support to react-router. It accurately reflects the primary objective across all modified and new files.
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/react-router/src/server/keyless/index.ts`:
- Around line 56-76: The current implementations of createAccountlessApplication
and completeOnboarding use a unsafe "{} as any" fallback when args is missing
and also capture args/options in the surrounding closure, which can cause
runtime errors and stale singletons. Fix by removing the "{} as any" fallback
and either (A) make the args parameter required on the public API for
createAccountlessApplication and completeOnboarding, or (B) construct a safe
keyless args object (e.g., build a minimal shape containing any properties
clerkClient expects) and pass that to clerkClient; also ensure you instantiate
clerkClient inside each method (not captured once in the outer scope) so each
call uses the provided args/options; update references to clerkClient, args,
options, createAccountlessApplication, completeOnboarding and
__experimental_accountlessApplications accordingly.

In `@packages/react-router/src/server/keyless/utils.ts`:
- Around line 77-84: The current check uses `if (!publishableKey || !secretKey)`
which can mix a configured key with a keyless key and break auth; change the
logic so you only enter keyless mode when both keys are absent (i.e., require
`!publishableKey && !secretKey`) before calling
`keylessService.getOrCreateKeys()` and assigning `publishableKey`, `secretKey`,
`claimUrl`, and `apiKeysUrl` from `keylessApp`; this ensures both
`publishableKey` and `secretKey` come from the same `keylessApp` source instead
of being mixed with configured values.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@integration/testUtils/index.ts`:
- Around line 90-91: Remove the barrel re-export of
mockClaimedInstanceEnvironmentCall from integration/testUtils/index.ts (the
export line exporting mockClaimedInstanceEnvironmentCall) and update all test
call sites to import mockClaimedInstanceEnvironmentCall directly from
integration/testUtils/keylessHelpers; specifically change imports in
integration/tests/next-quickstart-keyless.test.ts,
integration/tests/react-router/keyless.test.ts, and
integration/tests/tanstack-start/keyless.test.ts to import {
mockClaimedInstanceEnvironmentCall } from './keylessHelpers' (or the correct
relative path into testUtils/keylessHelpers) so no code relies on the index.ts
re-export and to avoid circular dependencies.

In `@integration/testUtils/keylessHelpers.ts`:
- Around line 7-19: The exported helper mockClaimedInstanceEnvironmentCall is
missing an explicit TypeScript return type; update its signature to explicitly
return Promise<void> (e.g., annotate the async function as (page: Page):
Promise<void>) so the exported symbol has a concrete type, and ensure the Page
type is imported/available where mockClaimedInstanceEnvironmentCall is declared.

@wobsoriano wobsoriano changed the title feat(react-router): [WIP] Add support for keyless mode feat(react-router): Add support for keyless mode Feb 7, 2026
Comment on lines +13 to +29
export async function createFileStorage(options: FileStorageOptions = {}): Promise<KeylessStorage> {
const { cwd = () => process.cwd() } = options;

try {
const [fs, path] = await Promise.all([import('node:fs'), import('node:path')]);

return createNodeFileStorage(fs, path, {
cwd,
frameworkPackageName: '@clerk/react-router',
});
} catch {
throw new Error(
'Keyless mode requires a Node.js runtime with file system access. ' +
'Set VITE_CLERK_KEYLESS_DISABLED=1 to disable keyless mode.',
);
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

This can be extracted as well, but will plan to do so while implementing keyless for other SDKs.

Having these node imports in the shared @clerk/shared/keyless barrel export would break Next.js at edge runtime, since packages/nextjs/src/server/keyless-node.ts imports from that barrel (correct me if Im wrong!)

* @param canUseKeyless - Whether keyless mode is enabled in the current environment
* @returns The resolved keys (either configured or from keyless mode)
*/
export async function resolveKeysWithKeylessFallback(
Copy link
Member

Choose a reason for hiding this comment

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

Consider making this a method on the keyless service itself, instead of passing it in as an argument

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated it to be a method on the keyless service instead of a standalone function 👍🏼

Also, we want keyless to work on Node.js runtime only, right?

Copy link
Member

Choose a reason for hiding this comment

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

@wobsoriano eventually I'd like to see keyless work in the browser for SPAs, so I don't want to couple this to Node.

Copy link
Member Author

Choose a reason for hiding this comment

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

gotcha, the current implementation should be agnostic then.

…eyless service instead of a

  standalone function
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@integration/tests/tanstack-start/keyless.test.ts`:
- Around line 41-43: The call to testToggleCollapsePopoverAndClaim is missing
the required framework parameter; update the invocation of
testToggleCollapsePopoverAndClaim({ page, context, app, dashboardUrl }) to
include framework (e.g. framework: framework or framework: '<your-framework>')
so the helper receives the framework value used in its URL validation logic;
ensure the framework identifier you pass matches the existing framework
variable/constant used elsewhere in the test suite.

In `@packages/shared/src/keyless/index.ts`:
- Around line 12-16: The barrel file exports KeylessResult twice which causes
TS2308; remove the duplicate type re-export from the
resolveKeysWithKeylessFallback export group so KeylessResult is only exported
once (keep the export type from './service'), i.e. update the exports involving
resolveKeysWithKeylessFallback to export only the value
(resolveKeysWithKeylessFallback) and not re-export KeylessResult.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/shared/src/keyless/index.ts`:
- Around line 15-16: Remove the new barrel re-exports: delete the lines
exporting resolveKeysWithKeylessFallback and the KeylessResult type from the
index.ts barrel; instead update callers to import resolveKeysWithKeylessFallback
and KeylessResult directly from the module that defines them (the
resolveKeysWithKeylessFallback module) or add a dedicated non-barrel entrypoint
if a single export surface is required, ensuring you reference the
resolveKeysWithKeylessFallback function and KeylessResult type by their original
module path rather than via this index barrel.

Comment on lines +15 to +16
export { resolveKeysWithKeylessFallback } from './resolveKeysWithKeylessFallback';
export type { KeylessResult } from './resolveKeysWithKeylessFallback';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid adding new exports in this index.ts barrel.

This extends a barrel file, which is disallowed and can introduce circular dependencies. Expose these via direct module imports or a non‑barrel entrypoint instead of re‑exporting here.

🛠️ Minimal fix (remove the new re-exports)
-export { resolveKeysWithKeylessFallback } from './resolveKeysWithKeylessFallback';
-export type { KeylessResult } from './resolveKeysWithKeylessFallback';

As per coding guidelines, “Avoid barrel files (index.ts re-exports) as they can cause circular dependencies”.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export { resolveKeysWithKeylessFallback } from './resolveKeysWithKeylessFallback';
export type { KeylessResult } from './resolveKeysWithKeylessFallback';
export { resolveKeysWithKeylessFallback } from './resolveKeysWithKeylessFallback';
export type { KeylessResult } from './resolveKeysWithKeylessFallback';
🤖 Prompt for AI Agents
In `@packages/shared/src/keyless/index.ts` around lines 15 - 16, Remove the new
barrel re-exports: delete the lines exporting resolveKeysWithKeylessFallback and
the KeylessResult type from the index.ts barrel; instead update callers to
import resolveKeysWithKeylessFallback and KeylessResult directly from the module
that defines them (the resolveKeysWithKeylessFallback module) or add a dedicated
non-barrel entrypoint if a single export surface is required, ensuring you
reference the resolveKeysWithKeylessFallback function and KeylessResult type by
their original module path rather than via this index barrel.

@wobsoriano wobsoriano merged commit 8479734 into main Feb 10, 2026
67 of 68 checks passed
@wobsoriano wobsoriano deleted the rob/react-router-keyless branch February 10, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants