Skip to content

Commit

Permalink
fix(nextjs): Use nextjs fetcher to identify internal page navigation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
panteliselef authored Jun 6, 2024
1 parent 47d4e7d commit e2f1e8f
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-falcons-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/nextjs': patch
---

Enhance page detection by utilizing the patched fetch from nextjs.
17 changes: 7 additions & 10 deletions integration/tests/protect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,13 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('authoriz
test('Protect in RSCs and RCCs as `signed-out user`', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });

// Do not run this part for nextjs v14, the flow is broken in 14.2.3 because vercel removed a header that our page detection logic depends on
if (!u.nexJsVersion.startsWith('14')) {
/**
* Soft navigations
*/
await u.page.goToRelative('/');
await page.getByText('Page Protected').click();
await page.waitForURL('**/sign-in?**');
await u.po.signIn.waitForMounted();
}
/**
* Soft navigations
*/
await u.page.goToRelative('/');
await page.getByText('Page Protected').click();
await page.waitForURL('**/sign-in?**');
await u.po.signIn.waitForMounted();

/**
* Hard navigations
Expand Down
22 changes: 22 additions & 0 deletions packages/nextjs/src/server/nextFetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
type Fetcher = typeof globalThis.fetch;

/**
* Based on nextjs internal implementation https://github.com/vercel/next.js/blob/6185444e0a944a82e7719ac37dad8becfed86acd/packages/next/src/server/lib/patch-fetch.ts#L23
*/
type NextFetcher = Fetcher & {
readonly __nextPatched: true;
readonly __nextGetStaticStore: () => { getStore: () => StaticGenerationAsyncStorage };
};

/**
* Full type can be found https://github.com/vercel/next.js/blob/6185444e0a944a82e7719ac37dad8becfed86acd/packages/next/src/client/components/static-generation-async-storage.external.ts#L4
*/
interface StaticGenerationAsyncStorage {
readonly pagePath?: string;
}

function isNextFetcher(fetch: Fetcher | NextFetcher): fetch is NextFetcher {
return '__nextPatched' in fetch && fetch.__nextPatched === true;
}

export { isNextFetcher };
23 changes: 18 additions & 5 deletions packages/nextjs/src/server/protect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
} from '@clerk/types';

import { constants as nextConstants } from '../constants';
import { isNextFetcher } from './nextFetcher';

type AuthProtectOptions = { unauthorizedUrl?: string; unauthenticatedUrl?: string };

Expand Down Expand Up @@ -121,12 +122,24 @@ const isPageRequest = (req: Request): boolean => {
return (
req.headers.get(constants.Headers.SecFetchDest) === 'document' ||
req.headers.get(constants.Headers.Accept)?.includes('text/html') ||
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req)) ||
!!req.headers.get(nextConstants.Headers.NextjsData)
isAppRouterInternalNavigation(req) ||
isPagesRouterInternalNavigation(req)
);
};

// In case we want to handle router handlers and server actions differently in the future
// const isRouteHandler = (req: Request) => {
// return !isPageRequest(req) && !isServerAction(req);
const isAppRouterInternalNavigation = (req: Request) =>
(!!req.headers.get(nextConstants.Headers.NextUrl) && !isServerActionRequest(req)) || isPagePathAvailable();

const isPagePathAvailable = () => {
const __fetch = globalThis.fetch;
return Boolean(isNextFetcher(__fetch) ? __fetch.__nextGetStaticStore().getStore().pagePath : false);
};

const isPagesRouterInternalNavigation = (req: Request) => !!req.headers.get(nextConstants.Headers.NextjsData);

// /**
// * In case we want to handle router handlers and server actions differently in the future
// */
// const isApiRouteRequest = (req: Request) => {
// return !isPageRequest(req) && !isServerActionRequest(req);
// };

0 comments on commit e2f1e8f

Please sign in to comment.