Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/quiet-lobsters-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@clerk/shared": minor
"@clerk/elements": patch
---

Moves the common `ClerkRouter` interface into `@clerk/shared/router`. Elements has been refactored internally to import the router from the shared package.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ClerkRouter } from '@clerk/shared/router';
import type {
ClerkResource,
LoadedClerk,
Expand All @@ -10,7 +11,6 @@ import type { ActorRefFrom } from 'xstate';

import type { ClerkElementsError } from '~/internals/errors';
import type { TFormMachine } from '~/internals/machines/form';
import type { ClerkRouter } from '~/react/router';

// ---------------------------------- Events ---------------------------------- //

Expand Down
4 changes: 1 addition & 3 deletions packages/elements/src/react/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
export { useNextRouter } from './next';
export { Route, Router, useClerkRouter } from './react';
export { Route, Router, useClerkRouter } from '@clerk/shared/router';
export { useVirtualRouter } from './virtual';

export type { ClerkRouter, ClerkHostRouter } from './router';
3 changes: 1 addition & 2 deletions packages/elements/src/react/router/next.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { ClerkHostRouter } from '@clerk/shared/router';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';

import { NEXT_WINDOW_HISTORY_SUPPORT_VERSION } from '~/internals/constants';

import type { ClerkHostRouter } from './router';

/**
* Clerk router integration with Next.js's router.
*/
Expand Down
3 changes: 1 addition & 2 deletions packages/elements/src/react/router/virtual.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use client';

import type { ClerkHostRouter } from '@clerk/shared/router';
import { useSyncExternalStore } from 'react';

import type { ClerkHostRouter } from './router';

const DUMMY_ORIGIN = 'https://clerk.dummy';

// TODO: introduce history stack?
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { type ClerkRouter, type ClerkHostRouter, createClerkRouter } from './router/router';
export { type RoutingMode } from './router/types';
export { Router, useClerkRouter, Route, ClerkRouterContext } from './router/react';
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { createContext, useContext } from 'react';
/**
* React-specific binding's for interacting with Clerk's router interface.
*/
import React, { createContext, useContext } from 'react';

import type { ClerkHostRouter, ClerkRouter } from './router';
import { createClerkRouter } from './router';
Expand All @@ -15,6 +18,9 @@ export function useClerkRouter() {
return ctx;
}

/**
* Construct a Clerk Router using the provided host router. The router instance is accessible using `useClerkRouter()`.
*/
export function Router({
basePath,
children,
Expand All @@ -31,8 +37,10 @@ export function Router({

type RouteProps = { path?: string; index?: boolean };

/**
* Used to conditionally render its children based on whether or not the current path matches the provided path.
*/
export function Route({ path, children, index }: RouteProps & { children: React.ReactNode }) {
// check for parent router, if exists, create child router, otherwise create one
const parentRouter = useClerkRouter();

if (!path && !index) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { withLeadingSlash, withoutTrailingSlash } from '@clerk/shared/url';

import type { ROUTING } from '~/internals/constants';
import { isAbsoluteUrl } from '~/utils/is-absolute-url';
import { isAbsoluteUrl, withLeadingSlash, withoutTrailingSlash } from '../url';
import type { RoutingMode } from './types';

export const PRESERVED_QUERYSTRING_PARAMS = ['after_sign_in_url', 'after_sign_up_url', 'redirect_url'];

/**
* This type represents a generic router interface that Clerk relies on to interact with the host router.
*/
export type ClerkHostRouter = {
readonly mode: ROUTING;
readonly mode: RoutingMode;
readonly name: string;
pathname: () => string;
push: (path: string) => void;
Expand Down Expand Up @@ -38,7 +36,7 @@ export type ClerkRouter = {
/**
* Mode of the router instance, path-based or virtual
*/
readonly mode: ROUTING;
readonly mode: RoutingMode;

/**
* Name of the router instance
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/router/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type RoutingMode = 'path' | 'virtual';
7 changes: 7 additions & 0 deletions packages/shared/src/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,10 @@ export function joinURL(base: string, ...input: string[]): string {

return url;
}

/* Code below is taken from https://github.com/vercel/next.js/blob/fe7ff3f468d7651a92865350bfd0f16ceba27db5/packages/next/src/shared/lib/utils.ts. LICENSE: MIT */

// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
const ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/;
export const isAbsoluteUrl = (url: string) => ABSOLUTE_URL_REGEX.test(url);
1 change: 1 addition & 0 deletions packages/shared/subpaths.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const subpathNames = [
'telemetry',
'logger',
'webauthn',
'router',
];

export const subpathFoldersBarrel = ['react'];
Expand Down