From 91efcfd1674d4592ca37e47781f34f1277a167fe Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 10 Dec 2024 10:30:26 +0100 Subject: [PATCH 1/5] smaller cleanups --- .../react-router/src/client/ReactRouterClerkProvider.tsx | 2 +- packages/react-router/src/client/types.ts | 5 +++-- packages/react-router/src/ssr/types.ts | 9 +++++++++ packages/react-router/src/utils/errors.ts | 1 - 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/react-router/src/client/ReactRouterClerkProvider.tsx b/packages/react-router/src/client/ReactRouterClerkProvider.tsx index f6b983b5401..e80bb6a7eb9 100644 --- a/packages/react-router/src/client/ReactRouterClerkProvider.tsx +++ b/packages/react-router/src/client/ReactRouterClerkProvider.tsx @@ -32,7 +32,7 @@ type ClerkProviderPropsWithState = ReactRouterClerkProviderProps & { clerkState?: ClerkState; }; -function ClerkProviderBase({ children, ...rest }: ClerkProviderPropsWithState): JSX.Element { +function ClerkProviderBase({ children, ...rest }: ClerkProviderPropsWithState) { const awaitableNavigate = useAwaitableNavigate(); const isSpaMode = _isSpaMode(); diff --git a/packages/react-router/src/client/types.ts b/packages/react-router/src/client/types.ts index 7a8c4ad8bea..65020edbd71 100644 --- a/packages/react-router/src/client/types.ts +++ b/packages/react-router/src/client/types.ts @@ -36,8 +36,9 @@ export type WithClerkState = { export type ReactRouterClerkProviderProps = Without & { /** - * Used to override the default CLERK_PUBLISHABLE_KEY env variable if needed. - * This is optional for React Router as the ClerkProvider will automatically use the CLERK_PUBLISHABLE_KEY env variable if it exists. + * Used to override the default VITE_CLERK_PUBLISHABLE_KEY env variable if needed. + * This is optional for React Router (in SSR mode) as the ClerkProvider will automatically use the VITE_CLERK_PUBLISHABLE_KEY env variable if it exists. + * If you use React Router in SPA mode or as a library, you have to pass the publishableKey prop. */ publishableKey?: string; children: React.ReactNode; diff --git a/packages/react-router/src/ssr/types.ts b/packages/react-router/src/ssr/types.ts index f130ecaa38a..10b8a9f855d 100644 --- a/packages/react-router/src/ssr/types.ts +++ b/packages/react-router/src/ssr/types.ts @@ -41,8 +41,17 @@ export type RouteInfo = { export type GetAuthReturn = Promise; export type RootAuthLoaderOptions = { + /** + * Used to override the default VITE_CLERK_PUBLISHABLE_KEY env variable if needed. + */ publishableKey?: string; + /** + * Used to override the CLERK_JWT_KEY env variable if needed. + */ jwtKey?: string; + /** + * Used to override the CLERK_SECRET_KEY env variable if needed. + */ secretKey?: string; /** * @deprecated This option will be removed in the next major version. diff --git a/packages/react-router/src/utils/errors.ts b/packages/react-router/src/utils/errors.ts index 14c7aac65fb..8e0a7682f2b 100644 --- a/packages/react-router/src/utils/errors.ts +++ b/packages/react-router/src/utils/errors.ts @@ -3,7 +3,6 @@ const createErrorMessage = (msg: string) => { For more info, check out the docs: https://clerk.com/docs, or come say hi in our discord server: https://clerk.com/discord - `; }; From 82e8f404f5b468b564ef48392b36bfa3e40ea91e Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 10 Dec 2024 10:30:56 +0100 Subject: [PATCH 2/5] improve env var loading --- packages/react-router/src/ssr/loadOptions.ts | 19 ++++++++++--------- packages/react-router/src/utils/env.ts | 12 ++++++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/react-router/src/ssr/loadOptions.ts b/packages/react-router/src/ssr/loadOptions.ts index 8bcd0ed44eb..79a15dd0113 100644 --- a/packages/react-router/src/ssr/loadOptions.ts +++ b/packages/react-router/src/ssr/loadOptions.ts @@ -13,12 +13,13 @@ export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderO const { request, context } = args; const clerkRequest = createClerkRequest(patchRequest(request)); - // Fetch environment variables across Remix runtime. - // 1. First check if the user passed the key in the getAuth function or the rootAuthLoader. + // Fetch environment variables across React Router runtime. + // 1. First check if the user passed the key in the getAuth() function or the rootAuthLoader(). // 2. Then try from process.env if exists (Node). - // 3. Then try from globalThis (Cloudflare Workers). - // 4. Then from loader context (Cloudflare Pages). - const secretKey = overrides.secretKey || getEnvVariable('CLERK_SECRET_KEY', context) || ''; + // 3. Then try from import.meta.env if exists (Vite). + // 4. Then try from globalThis (Cloudflare Workers). + // 5. Then from loader context (Cloudflare Pages). + const secretKey = overrides.secretKey || getEnvVariable('CLERK_SECRET_KEY', context); const publishableKey = overrides.publishableKey || getPublicEnvVariables(context).publishableKey; const jwtKey = overrides.jwtKey || getEnvVariable('CLERK_JWT_KEY', context); const apiUrl = getEnvVariable('CLERK_API_URL', context) || apiUrlFromPublishableKey(publishableKey); @@ -33,13 +34,13 @@ export const loadOptions = (args: LoaderFunctionArgs, overrides: RootAuthLoaderO const signInUrl = overrides.signInUrl || getPublicEnvVariables(context).signInUrl; const signUpUrl = overrides.signUpUrl || getPublicEnvVariables(context).signUpUrl; const signInForceRedirectUrl = - overrides.signInForceRedirectUrl || getEnvVariable('CLERK_SIGN_IN_FORCE_REDIRECT_URL', context) || ''; + overrides.signInForceRedirectUrl || getPublicEnvVariables(context).signInForceRedirectUrl; const signUpForceRedirectUrl = - overrides.signUpForceRedirectUrl || getEnvVariable('CLERK_SIGN_UP_FORCE_REDIRECT_URL', context) || ''; + overrides.signUpForceRedirectUrl || getPublicEnvVariables(context).signUpForceRedirectUrl; const signInFallbackRedirectUrl = - overrides.signInFallbackRedirectUrl || getEnvVariable('CLERK_SIGN_IN_FALLBACK_REDIRECT_URL', context) || ''; + overrides.signInFallbackRedirectUrl || getPublicEnvVariables(context).signInFallbackRedirectUrl; const signUpFallbackRedirectUrl = - overrides.signUpFallbackRedirectUrl || getEnvVariable('CLERK_SIGN_UP_FALLBACK_REDIRECT_URL', context) || ''; + overrides.signUpFallbackRedirectUrl || getPublicEnvVariables(context).signUpFallbackRedirectUrl; const afterSignInUrl = overrides.afterSignInUrl || getPublicEnvVariables(context).afterSignInUrl; const afterSignUpUrl = overrides.afterSignUpUrl || getPublicEnvVariables(context).afterSignUpUrl; diff --git a/packages/react-router/src/utils/env.ts b/packages/react-router/src/utils/env.ts index 92fb66a2178..d42c7a0831b 100644 --- a/packages/react-router/src/utils/env.ts +++ b/packages/react-router/src/utils/env.ts @@ -75,6 +75,18 @@ export const getPublicEnvVariables = (context: AppLoadContext | undefined) => { telemetryDebug: isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG', context)) || isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG', context)), + signInForceRedirectUrl: + getEnvVariable('VITE_CLERK_SIGN_IN_FORCE_REDIRECT_URL', context) || + getEnvVariable('CLERK_SIGN_IN_FORCE_REDIRECT_URL', context), + signUpForceRedirectUrl: + getEnvVariable('VITE_CLERK_SIGN_UP_FORCE_REDIRECT_URL', context) || + getEnvVariable('CLERK_SIGN_UP_FORCE_REDIRECT_URL', context), + signInFallbackRedirectUrl: + getEnvVariable('VITE_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL', context) || + getEnvVariable('CLERK_SIGN_IN_FALLBACK_REDIRECT_URL', context), + signUpFallbackRedirectUrl: + getEnvVariable('VITE_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL', context) || + getEnvVariable('CLERK_SIGN_UP_FALLBACK_REDIRECT_URL', context), afterSignInUrl: getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL', context) || getEnvVariable('CLERK_AFTER_SIGN_IN_URL', context), afterSignUpUrl: From 34e507e18b02d58419e886d516f2b045859f9dfa Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 10 Dec 2024 12:01:41 +0100 Subject: [PATCH 3/5] activate basic test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66a97afbb43..f349e00e101 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,7 +160,7 @@ jobs: strategy: fail-fast: false matrix: - test-name: [ 'generic', 'express', 'quickstart', 'ap-flows', 'elements', 'sessions', 'astro', 'expo-web', 'tanstack-start', 'tanstack-router', 'vue', 'nuxt'] + test-name: [ 'generic', 'express', 'quickstart', 'ap-flows', 'elements', 'sessions', 'astro', 'expo-web', 'tanstack-start', 'tanstack-router', 'vue', 'nuxt', 'react-router'] test-project: ['chrome'] include: - test-name: 'nextjs' From 1fd65546902cc0a4c45c1b862cdb0159655d1a4c Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 10 Dec 2024 15:22:12 +0100 Subject: [PATCH 4/5] improve test --- .../app/routes/protected.tsx | 17 ++++++-- integration/tests/react-router/basic.test.ts | 41 ++++++++++++++++++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/integration/templates/react-router-node/app/routes/protected.tsx b/integration/templates/react-router-node/app/routes/protected.tsx index e6267def3ac..2fdc2718e1c 100644 --- a/integration/templates/react-router-node/app/routes/protected.tsx +++ b/integration/templates/react-router-node/app/routes/protected.tsx @@ -1,21 +1,32 @@ import { redirect } from 'react-router'; +import { UserProfile } from '@clerk/react-router'; import { getAuth } from '@clerk/react-router/ssr.server'; +import { createClerkClient } from '@clerk/react-router/api.server'; import type { Route } from './+types/profile'; export async function loader(args: Route.LoaderArgs) { const { userId } = await getAuth(args); if (!userId) { - return redirect('/sign-in?redirect_url=' + args.request.url); + return redirect('/sign-in'); } - return {}; + const user = await createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY }).users.getUser(userId); + + return { + user, + }; } -export default function Profile(args: Route.ComponentProps) { +export default function Profile({ loaderData }: Route.ComponentProps) { return (

Protected

+ +
    +
  • First name: {loaderData.user.firstName}
  • +
  • Email: {loaderData.user.emailAddresses[0].emailAddress}
  • +
); } diff --git a/integration/tests/react-router/basic.test.ts b/integration/tests/react-router/basic.test.ts index dbd22816d65..595a724304b 100644 --- a/integration/tests/react-router/basic.test.ts +++ b/integration/tests/react-router/basic.test.ts @@ -1,4 +1,4 @@ -import { test } from '@playwright/test'; +import { expect, test } from '@playwright/test'; import { appConfigs } from '../../presets'; import type { FakeUser } from '../../testUtils'; @@ -49,5 +49,44 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes], withPattern: await u.po.userButton.toHaveVisibleMenuItems([/Manage account/i, /Sign out$/i]); }); + + test('redirects to sign-in when unauthenticated', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/protected'); + await u.page.waitForURL(`${app.serverUrl}/sign-in`); + await u.po.signIn.waitForMounted(); + }); + + test('renders control components contents', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToAppHome(); + await expect(u.page.getByText('SignedOut')).toBeVisible(); + + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.expect.toBeSignedIn(); + await expect(u.page.getByText('SignedIn')).toBeVisible(); + }); + + test('renders user profile with SSR data', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/sign-in'); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.expect.toBeSignedIn(); + + await u.po.userButton.waitForMounted(); + await u.page.goToRelative('/protected'); + await u.po.userProfile.waitForMounted(); + + // Fetched from an API endpoint (/api/me), which is server-rendered. + // This also verifies that the server middleware is working. + await expect(u.page.getByText(`First name: ${fakeUser.firstName}`)).toBeVisible(); + await expect(u.page.getByText(`Email: ${fakeUser.email}`)).toBeVisible(); + }); }, ); From 14ef52508c8397b8821d25042113e77a97f9bca3 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 10 Dec 2024 15:24:11 +0100 Subject: [PATCH 5/5] add changeset --- .changeset/lemon-news-agree.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lemon-news-agree.md diff --git a/.changeset/lemon-news-agree.md b/.changeset/lemon-news-agree.md new file mode 100644 index 00000000000..a5ddafb63de --- /dev/null +++ b/.changeset/lemon-news-agree.md @@ -0,0 +1,5 @@ +--- +'@clerk/react-router': patch +--- + +Improve environment variable loading for certain values