Skip to content

Improve key missing error handling with custom hooks #6521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

thiskevinwang
Copy link
Contributor

Description

This PR updates the Clerk Next.js SDK to provide more flexible handling for missing PUBLISHABLE_KEY and SECRET_KEY environment variables. Previously, the SDK would throw disruptive runtime errors, blocking Next.js app rendering.

Changes introduced:

  1. New Hooks in clerkMiddleware:
    • Introduces onMissingPublishableKey and onMissingSecretKey options to ClerkMiddlewareOptions.
    • If a key is missing and a corresponding hook is provided:
      • If the hook returns a NextResponse, it is used as the middleware result.
      • If the hook returns void, the middleware proceeds by returning NextResponse.next() and setting x-clerk-auth-status: signed-out, allowing the app to render without crashing.
    • If no hook is provided for a missing key, the SDK retains its previous behavior of throwing an error.
  2. Non-throwing Keyless Actions:
    • The unconditional error throw in packages/nextjs/src/app-router/keyless-actions.ts for a missing publishable key during keyless operations has been removed. It now returns null, allowing the application to continue gracefully.

How to test:

Configure the new hooks in your clerkMiddleware to observe the custom behavior when NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY or CLERK_SECRET_KEY are missing from your environment.

// pages/_middleware.ts or middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

export default clerkMiddleware((auth, req) => {
  // your existing middleware logic
}, {
  onMissingPublishableKey: (req) => {
    console.warn('Clerk: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY is missing. Continuing unauthenticated.');
    // Example: redirect to a setup page or return a custom response
    // return NextResponse.redirect('/clerk-setup');
    // Returning void (or nothing) will continue the request with an unauthenticated state.
  },
  onMissingSecretKey: (req) => {
    console.error('Clerk: CLERK_SECRET_KEY is missing. This is a critical error.');
    // Example: return a 500 error page or a maintenance page
    return new NextResponse('Internal Server Error: Clerk configuration missing.', { status: 500 });
  },
});

If these hooks are not provided, the existing throwing behavior will remain.

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:

Slack Thread

Open in Cursor Open in Web

Copy link

cursor bot commented Aug 11, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Copy link

vercel bot commented Aug 11, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Project Status Preview Comments Updated (UTC)
clerk-js-sandbox Ready Visit Preview 💬 Add feedback Aug 11, 2025 8:20pm

Copy link
Member

@panteliselef panteliselef left a comment

Choose a reason for hiding this comment

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

@thiskevinwang can you share here or in private what is the intention with these new hooks and what do they unlock ? Are they aimed for "internal" aka Clerk usage or for developers/customers ?

In this supposed to be a recovery mechanism when Keyless fails to work, or is it overriding Keyless ? Does it unlock a new way to create keys ?

Comment on lines +96 to +111

/**
* Optional hook invoked when the publishable key is missing. If it returns a response, it will be used as the middleware result.
* If it returns void, the request will continue unauthenticated.
*/
onMissingPublishableKey?: (
request: NextRequest,
) => NextMiddlewareReturn | Promise<NextMiddlewareReturn> | void | Promise<void>;

/**
* Optional hook invoked when the secret key is missing. If it returns a response, it will be used as the middleware result.
* If it returns void, the request will continue unauthenticated.
*/
onMissingSecretKey?: (
request: NextRequest,
) => NextMiddlewareReturn | Promise<NextMiddlewareReturn> | void | Promise<void>;
Copy link
Member

Choose a reason for hiding this comment

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

If I understand the usage correctly those should only be executed in DEV. Also if we are thinking these hooks as something that will enable extra Clerk functionality we should prefix them with __internal_.

Comment on lines +1 to +17
// tsup.config.ts
import { defineConfig } from 'tsup';
var tsup_config_default = defineConfig(() => {
return {
entry: {
index: 'src/index.ts',
},
minify: false,
clean: true,
sourcemap: true,
format: ['cjs', 'esm'],
legacyOutput: true,
dts: true,
};
});
export { tsup_config_default as default };
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidHN1cC5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9faW5qZWN0ZWRfZmlsZW5hbWVfXyA9IFwiL3dvcmtzcGFjZS9wYWNrYWdlcy90eXBlcy90c3VwLmNvbmZpZy50c1wiO2NvbnN0IF9faW5qZWN0ZWRfZGlybmFtZV9fID0gXCIvd29ya3NwYWNlL3BhY2thZ2VzL3R5cGVzXCI7Y29uc3QgX19pbmplY3RlZF9pbXBvcnRfbWV0YV91cmxfXyA9IFwiZmlsZTovLy93b3Jrc3BhY2UvcGFja2FnZXMvdHlwZXMvdHN1cC5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd0c3VwJztcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKCgpID0+IHtcbiAgcmV0dXJuIHtcbiAgICBlbnRyeToge1xuICAgICAgaW5kZXg6ICdzcmMvaW5kZXgudHMnLFxuICAgIH0sXG4gICAgbWluaWZ5OiBmYWxzZSxcbiAgICBjbGVhbjogdHJ1ZSxcbiAgICBzb3VyY2VtYXA6IHRydWUsXG4gICAgZm9ybWF0OiBbJ2NqcycsICdlc20nXSxcbiAgICBsZWdhY3lPdXRwdXQ6IHRydWUsXG4gICAgZHRzOiB0cnVlLFxuICB9O1xufSk7XG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQXlOLFNBQVMsb0JBQW9CO0FBRXRQLElBQU8sc0JBQVEsYUFBYSxNQUFNO0FBQ2hDLFNBQU87QUFBQSxJQUNMLE9BQU87QUFBQSxNQUNMLE9BQU87QUFBQSxJQUNUO0FBQUEsSUFDQSxRQUFRO0FBQUEsSUFDUixPQUFPO0FBQUEsSUFDUCxXQUFXO0FBQUEsSUFDWCxRQUFRLENBQUMsT0FBTyxLQUFLO0FBQUEsSUFDckIsY0FBYztBQUFBLElBQ2QsS0FBSztBQUFBLEVBQ1A7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=
Copy link
Member

Choose a reason for hiding this comment

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

was this autogenerated ?

@@ -57,7 +57,7 @@ export async function createOrReadKeylessAction(): Promise<null | Omit<Accountle
const result = await import('../server/keyless-node.js').then(m => m.createOrReadKeyless()).catch(() => null);

if (!result) {
errorThrower.throwMissingPublishableKeyError();
// In non-keyless scenarios, allow continuing without throwing so the app can render.
Copy link
Member

Choose a reason for hiding this comment

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

❓ Why is Keyless being affected by these changes ?

Comment on lines +171 to +177
// Fall through to allow request to continue unauthenticated
const res = NextResponse.next();
setRequestHeadersOnNextResponse(res, request, {
[constants.Headers.AuthStatus]: 'signed-out',
});
return res;
}
Copy link
Member

Choose a reason for hiding this comment

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

Can you clarify how this operates on the client ?

@clerk/clerk-react will throw if the publishable key is missing, since to the url encoded withing the PK is required to hotload clerk-js.

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.

4 participants