diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 188e673422..9be6386aa8 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -30,6 +30,7 @@ import type { HandleOAuthCallbackParams, InstanceType, ListenerCallback, + NavigateOptions, OrganizationInvitationResource, OrganizationMembershipResource, OrganizationProfileProps, @@ -635,13 +636,14 @@ export default class Clerk implements ClerkInterface { return unsubscribe; }; - public navigate = async (to: string | undefined): Promise => { + public navigate = async (to: string | undefined, options?: NavigateOptions): Promise => { if (!to || !inBrowser()) { return; } const toURL = new URL(to, window.location.href); - const customNavigate = this.#options.navigate; + const customNavigate = + options?.replace && this.#options.replaceNavigate ? this.#options.replaceNavigate : this.#options.navigate; if (toURL.origin !== window.location.origin || !customNavigate) { windowNavigate(toURL); diff --git a/packages/clerk-js/src/ui/router/PathRouter.tsx b/packages/clerk-js/src/ui/router/PathRouter.tsx index 41112a41ff..253c14686b 100644 --- a/packages/clerk-js/src/ui/router/PathRouter.tsx +++ b/packages/clerk-js/src/ui/router/PathRouter.tsx @@ -1,3 +1,4 @@ +import { NavigateOptions } from '@clerk/types'; import React from 'react'; import { hasUrlInFragment, mergeFragmentIntoUrl, stripOrigin } from '../../utils'; @@ -18,12 +19,12 @@ export const PathRouter = ({ basePath, preservedParams, children }: PathRouterPr throw new Error('Clerk: Missing navigate option.'); } - const internalNavigate = (toURL: URL | string | undefined) => { + const internalNavigate = (toURL: URL | string | undefined, options?: NavigateOptions) => { if (!toURL) { return; } // Only send the path - return navigate(stripOrigin(toURL)); + return navigate(stripOrigin(toURL), options); }; const getPath = () => { @@ -38,8 +39,7 @@ export const PathRouter = ({ basePath, preservedParams, children }: PathRouterPr const convertHashToPath = async () => { if (hasUrlInFragment(window.location.hash)) { const url = mergeFragmentIntoUrl(new URL(window.location.href)); - window.history.replaceState(window.history.state, '', url.href); - await internalNavigate(url.href); // make this navigation as well since replaceState is asynchronous + await internalNavigate(url.href, { replace: true }); setStripped(true); } }; diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 5337d0c6b8..3663e0d13c 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -467,7 +467,7 @@ export type BuildUrlWithAuthParams = { // TODO: Make sure Isomorphic Clerk navigate can work with the correct type: // (to: string) => Promise -export type CustomNavigation = (to: string) => Promise | void; +export type CustomNavigation = (to: string, options?: NavigateOptions) => Promise | void; export type ClerkThemeOptions = DeepSnakeToCamel>; @@ -475,6 +475,7 @@ export interface ClerkOptions { appearance?: Appearance; localization?: LocalizationResource; navigate?: (to: string) => Promise | unknown; + replaceNavigate?: (to: string) => Promise | unknown; polling?: boolean; selectInitialSession?: (client: ClientResource) => ActiveSessionResource | null; /** Controls if ClerkJS will load with the standard browser setup using Clerk cookies */ @@ -500,6 +501,10 @@ export interface ClerkOptions { isSatellite?: boolean | ((url: URL) => boolean); } +export interface NavigateOptions { + replace?: boolean; +} + export interface Resources { client: ClientResource; session?: ActiveSessionResource | null;