Skip to content

Commit 3ecefa4

Browse files
authored
fix(tanstack-react-start): Handle serialization issue when doing handshake (#6345)
1 parent 8305b02 commit 3ecefa4

File tree

10 files changed

+191
-105
lines changed

10 files changed

+191
-105
lines changed

.changeset/fifty-buses-fix.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/tanstack-react-start': minor
3+
---
4+
5+
- Fixes serialization errors during handshake
6+
- Bump `@tanstack/react-start` and `@tanstack/react-router` peer dependency to 1.127.0

integration/templates/tanstack-react-router/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
"start": "vite"
1010
},
1111
"dependencies": {
12-
"@tanstack/react-router": "^1.121.27",
13-
"@tanstack/react-router-devtools": "^1.121.27",
14-
"@tanstack/router-plugin": "^1.121.27",
12+
"@tanstack/react-router": "^1.128.0",
13+
"@tanstack/react-router-devtools": "^1.128.0",
14+
"@tanstack/router-plugin": "^1.128.0",
1515
"react": "18.3.1",
1616
"react-dom": "18.3.1"
1717
},

integration/templates/tanstack-react-start/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
"start": "vite start --port=$PORT"
99
},
1010
"dependencies": {
11-
"@tanstack/react-router": "^1.121.27",
12-
"@tanstack/react-router-devtools": "^1.121.27",
13-
"@tanstack/react-start": "^1.121.28",
11+
"@tanstack/react-router": "^1.128.0",
12+
"@tanstack/react-router-devtools": "^1.128.0",
13+
"@tanstack/react-start": "^1.128.0",
1414
"react": "18.3.1",
1515
"react-dom": "18.3.1",
1616
"tailwind-merge": "^2.5.4"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { createFileRoute, redirect } from '@tanstack/react-router';
2+
import { createServerFn } from '@tanstack/react-start';
3+
import { getAuth } from '@clerk/tanstack-react-start/server';
4+
import { getWebRequest } from '@tanstack/react-start/server';
5+
6+
const fetchClerkAuth = createServerFn({ method: 'GET' }).handler(async () => {
7+
const request = getWebRequest();
8+
if (!request) throw new Error('No request found');
9+
10+
const { userId } = await getAuth(request);
11+
12+
return {
13+
userId,
14+
};
15+
});
16+
17+
export const Route = createFileRoute('/')({
18+
component: Page,
19+
beforeLoad: async () => await fetchClerkAuth(),
20+
loader: async ({ context }) => {
21+
return { userId: context.userId };
22+
},
23+
});
24+
25+
function Page() {
26+
const state = Route.useLoaderData();
27+
28+
return state.userId ? <h1>Welcome! Your ID is {state.userId}!</h1> : <h1>You are not signed in</h1>;
29+
}

integration/tests/tanstack-start/basic.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { User } from '@clerk/backend';
12
import { expect, test } from '@playwright/test';
23

34
import { appConfigs } from '../../presets';
@@ -16,6 +17,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })(
1617
test.describe.configure({ mode: 'parallel' });
1718

1819
let fakeUser: FakeUser;
20+
let bapiUser: User;
1921

2022
test.beforeAll(async () => {
2123
const u = createTestUtils({ app });
@@ -24,7 +26,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })(
2426
withPhoneNumber: true,
2527
withUsername: true,
2628
});
27-
await u.services.users.createBapiUser(fakeUser);
29+
bapiUser = await u.services.users.createBapiUser(fakeUser);
2830
});
2931

3032
test.afterAll(async () => {
@@ -72,6 +74,25 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })(
7274
expect(clerkInitialState !== undefined).toBeTruthy();
7375
});
7476

77+
test('retrieve auth state in server functions', async ({ page, context }) => {
78+
const u = createTestUtils({ app, page, context });
79+
80+
await u.page.goToRelative('/user');
81+
82+
await expect(u.page.getByText('You are not signed in')).toBeVisible();
83+
84+
await u.po.signIn.goTo();
85+
86+
await u.po.signIn.setIdentifier(fakeUser.email);
87+
await u.po.signIn.setPassword(fakeUser.password);
88+
await u.po.signIn.continue();
89+
await u.po.expect.toBeSignedIn();
90+
91+
await u.page.goToRelative('/user');
92+
93+
await expect(u.page.getByText(`Welcome! Your ID is ${bapiUser.id}!`)).toBeVisible();
94+
});
95+
7596
test('clerk handler sets headers', async ({ page, context }) => {
7697
const u = createTestUtils({ app, page, context });
7798
const r = await u.po.signIn.goTo();

packages/tanstack-react-start/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@
7474
"tslib": "catalog:repo"
7575
},
7676
"devDependencies": {
77-
"@tanstack/react-router": "^1.121.41",
78-
"@tanstack/react-start": "^1.121.41",
77+
"@tanstack/react-router": "^1.127.0",
78+
"@tanstack/react-start": "^1.127.0",
7979
"esbuild-plugin-file-path-extensions": "^2.1.4"
8080
},
8181
"peerDependencies": {
82-
"@tanstack/react-router": "^1.121.0",
83-
"@tanstack/react-start": "^1.121.0",
82+
"@tanstack/react-router": "^1.127.0",
83+
"@tanstack/react-start": "^1.127.0",
8484
"react": "catalog:peer-react",
8585
"react-dom": "catalog:peer-react"
8686
},

packages/tanstack-react-start/src/server/authenticateRequest.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { AuthStatus, constants } from '@clerk/backend/internal';
44
import { handleNetlifyCacheInDevInstance } from '@clerk/shared/netlifyCacheHandler';
55

66
import { errorThrower } from '../utils';
7+
import { ClerkHandshakeRedirect } from './errors';
78
import { patchRequest } from './utils';
89

910
export async function authenticateRequest(
@@ -41,9 +42,9 @@ export async function authenticateRequest(
4142
requestStateHeaders: requestState.headers,
4243
publishableKey: requestState.publishableKey,
4344
});
45+
4446
// triggering a handshake redirect
45-
// eslint-disable-next-line @typescript-eslint/only-throw-error
46-
throw new Response(null, { status: 307, headers: requestState.headers });
47+
throw new ClerkHandshakeRedirect(307, requestState.headers);
4748
}
4849

4950
if (requestState.status === AuthStatus.Handshake) {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export class ClerkHandshakeRedirect extends Error {
2+
constructor(
3+
public status: number,
4+
public headers: Headers,
5+
) {
6+
super('Clerk handshake redirect required');
7+
this.name = 'ClerkHandshakeRedirect';
8+
this.status = status;
9+
this.headers = headers;
10+
}
11+
}

packages/tanstack-react-start/src/server/middlewareHandler.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { AnyRouter } from '@tanstack/react-router';
22
import type { CustomizeStartHandler, HandlerCallback, RequestHandler } from '@tanstack/react-start/server';
33

44
import { authenticateRequest } from './authenticateRequest';
5+
import { ClerkHandshakeRedirect } from './errors';
56
import { loadOptions } from './loadOptions';
67
import type { LoaderOptions } from './types';
78
import { getResponseClerkState } from './utils';
@@ -33,9 +34,12 @@ export function createClerkHandler<TRouter extends AnyRouter>(
3334

3435
await router.load();
3536
} catch (error) {
36-
if (error instanceof Response) {
37+
if (error instanceof ClerkHandshakeRedirect) {
3738
// returning the response
38-
return error;
39+
return new Response(null, {
40+
status: error.status,
41+
headers: error.headers,
42+
});
3943
}
4044

4145
// rethrowing the error if it is not a Response

0 commit comments

Comments
 (0)