Skip to content

fix(react-router): lazyRouteComponent reload guard key fallsback to importer's import.meta.url on Safari #3450

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const router = createRouter({
defaultPreload: 'intent',
defaultStaleTime: 5000,
scrollRestoration: true,
defaultErrorComponent: () => <div>This is the error component</div>,
})

// Register things for typesafety
Expand Down
40 changes: 40 additions & 0 deletions e2e/react-router/basic-file-based-code-splitting/tests/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,43 @@ test('Navigating to a not-found route', async ({ page }) => {
await page.getByRole('link', { name: 'Start Over' }).click()
await expect(page.getByRole('heading')).toContainText('Welcome Home!')
})

test('Navigating to a route where the lazy component fails to load', async ({
page,
}) => {
// block (and count) all requests to the posts.index route component
let requested = 0
await page.route('**/assets/posts.index-*', (route) => {
requested++
return route.fulfill({
status: 404,
contentType: 'text/plain',
body: 'Not Found!',
})
})

// count how many times the page reloads
let reloaded = 0
page.on('load', () => {
reloaded++
})

// navigate to the posts page
await page.getByRole('link', { name: 'Posts' }).click()

// will reload only once, despite failing twice, because the name of the module is the same both times
await expect(() => {
expect(reloaded).toBe(1)
expect(requested).toBe(2)
}).toPass({
intervals: [50],
timeout: 3000,
})

// the error component should be rendered
await page.getByText('This is the error component')

// make sure it doesn't reload again (handle edge-case where `.toPass` above was executed right before a 2nd page reload)
await page.waitForTimeout(200)
expect(reloaded).toBe(1)
})
23 changes: 16 additions & 7 deletions packages/react-router/src/lazyRouteComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,24 @@ import type { AsyncRouteComponent } from './route'
// If this happens, the old version in the user's browser would have an outdated
// URL to the lazy module.
// In that case, we want to attempt one window refresh to get the latest.
function isModuleNotFoundError(error: any): boolean {
function getModuleNotFoundErrorKey(
error: any,
importerName?: string,
): string | false {
// chrome: "Failed to fetch dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
// firefox: "error loading dynamically imported module: http://localhost:5173/src/routes/posts.index.tsx?tsr-split"
// safari: "Importing a module script failed."
if (typeof error?.message !== 'string') return false
return (
if (
error.message.startsWith('Failed to fetch dynamically imported module') ||
error.message.startsWith('error loading dynamically imported module') ||
error.message.startsWith('Importing a module script failed')
error.message.startsWith('error loading dynamically imported module')
)
// Use error.message as key because it contains the module path that failed.
return error.message

if (error.message.startsWith('Importing a module script failed'))
return importerName ?? error.message
return false
}

export function ClientOnly({
Expand Down Expand Up @@ -45,6 +53,7 @@ export function lazyRouteComponent<
importer: () => Promise<T>,
exportName?: TKey,
ssr?: () => boolean,
importerName?: string,
): T[TKey] extends (props: infer TProps) => any
? AsyncRouteComponent<TProps>
: never {
Expand All @@ -69,7 +78,8 @@ export function lazyRouteComponent<
// there's nothing we want to do about module not found during preload.
// Record the error, the rest is handled during the render path.
error = err
if (isModuleNotFoundError(error)) {
const key = getModuleNotFoundErrorKey(err, importerName)
if (key) {
if (
error instanceof Error &&
typeof window !== 'undefined' &&
Expand All @@ -78,8 +88,7 @@ export function lazyRouteComponent<
// Again, we want to reload one time on module not found error and not enter
// a reload loop if there is some other issue besides an old deploy.
// That's why we store our reload attempt in sessionStorage.
// Use error.message as key because it contains the module path that failed.
const storageKey = `tanstack_router_reload:${error.message}`
const storageKey = `tanstack_router_reload:${key}`
if (!sessionStorage.getItem(storageKey)) {
sessionStorage.setItem(storageKey, '1')
reload = true
Expand Down
12 changes: 8 additions & 4 deletions packages/router-generator/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,17 +576,21 @@ export async function generator(config: Config, root: string) {
)
.filter((d) => d[1])
.map((d) => {
return `${
d[0]
}: lazyRouteComponent(() => import('./${replaceBackslash(
const importPath = replaceBackslash(
removeExt(
path.relative(
path.dirname(config.generatedRouteTree),
path.resolve(config.routesDirectory, d[1]!.filePath),
),
config.addExtensions,
),
)}'), '${d[0]}')`
)
return `${d[0]}: lazyRouteComponent(
() => import('./${importPath}'),
'${d[0]}',
undefined,
import.meta.url
)`
})
.join('\n,')}
})`
Expand Down
4 changes: 2 additions & 2 deletions packages/router-plugin/src/core/code-splitter/compilers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,11 @@ export function compileCodeSplitReferenceRoute(
// If it's a component, we need to pass the function to check the Route.ssr value
if (key === 'component') {
prop.value = template.expression(
`${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}', () => Route.ssr)`,
`${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}', () => Route.ssr, import.meta.url)`,
)()
} else {
prop.value = template.expression(
`${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}')`,
`${LAZY_ROUTE_COMPONENT_IDENT}(${splitNodeMeta.localImporterIdent}, '${splitNodeMeta.exporterIdent}', undefined, import.meta.url)`,
)()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createFileRoute } from '@tanstack/react-router';
import { fetchPosts } from '../posts';
export const Route = createFileRoute('/posts')({
loader: fetchPosts,
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
export function TSRDummyComponent() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const $$splitComponentImporter = () => import('chinese.tsx?tsr-split=component')
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
interface DemoProps {
title: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { isEnabled } from '@features/feature-flags';
import TrueImport from '@modules/true-component';
import { falseLoader } from '@modules/false-component';
export const Route = createFileRoute('/posts')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: isEnabled ? TrueImport.loader : falseLoader
});
export function TSRDummyComponent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { importedLoader } from '../../shared/imported';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: importedLoader
});
export function TSRDummyComponent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createFileRoute } from '@tanstack/react-router';
import { fetchPosts } from '../posts';
export const Route = createFileRoute('/posts')({
loader: fetchPosts,
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
export function TSRDummyComponent() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const $$splitComponentImporter = () => import('importAttribute.tsx?tsr-split=com
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
export function TSRDummyComponent() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { importedLoader } from '../../shared/imported';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: importedLoader
});
export function TSRDummyComponent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const $$splitComponentImporter = () => import('imported-default-component.tsx?ts
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
export function TSRDummyComponent() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ const $$splitComponentImporter = () => import('imported-errorComponent.tsx?tsr-s
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
errorComponent: lazyRouteComponent($$splitErrorComponentImporter, 'errorComponent')
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
errorComponent: lazyRouteComponent($$splitErrorComponentImporter, 'errorComponent', undefined, import.meta.url)
});
export function TSRDummyComponent() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ const $$splitComponentImporter = () => import('imported-notFoundComponent.tsx?ts
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
notFoundComponent: lazyRouteComponent($$splitNotFoundComponentImporter, 'notFoundComponent')
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
notFoundComponent: lazyRouteComponent($$splitNotFoundComponentImporter, 'notFoundComponent', undefined, import.meta.url)
});
export function TSRDummyComponent() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ const $$splitComponentImporter = () => import('imported-pendingComponent.tsx?tsr
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
pendingComponent: lazyRouteComponent($$splitPendingComponentImporter, 'pendingComponent')
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
pendingComponent: lazyRouteComponent($$splitPendingComponentImporter, 'pendingComponent', undefined, import.meta.url)
});
export function TSRDummyComponent() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { importedLoader } from '../../shared';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: importedLoader
});
export function TSRDummyComponent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const $$splitComponentImporter = () => import('inline.tsx?tsr-split=component');
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
Route.addChildren([]);
export const test = 'test';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const Route = createFileRoute('/')({
sponsorsPromise: defer(getSponsorsForSponsorPack())
};
},
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
export function TSRDummyComponent() {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { importedLoader } from '../../shared/imported';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: importedLoader
});
export function TSRDummyComponent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { importedLoader } from '../../shared/imported';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: importedLoader
});
export function TSRDummyComponent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function loaderFn() {
};
}
export const Route = createFileRoute('/_layout')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: loaderFn
});
export const SIDEBAR_WIDTH = '150px';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { startProject } from '~/projects/start';
import { createFileRoute } from '@tanstack/react-router';
import { seo } from '~/utils/seo';
export const Route = createFileRoute('/_libraries/start/$version/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
meta: () => seo({
title: startProject.name,
description: startProject.description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import { createFileRoute } from '@tanstack/react-router';
import { fetchPosts } from '../posts';
export const Route = createFileRoute('/posts')({
loader: fetchPosts,
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const $$splitComponentImporter = () => import('chinese.tsx?tsr-split=component')
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
interface DemoProps {
title: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import { isEnabled } from '@features/feature-flags';
import TrueImport from '@modules/true-component';
import { falseLoader } from '@modules/false-component';
export const Route = createFileRoute('/posts')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: isEnabled ? TrueImport.loader : falseLoader
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { importedLoader } from '../../shared/imported';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: importedLoader
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import { createFileRoute } from '@tanstack/react-router';
import { fetchPosts } from '../posts';
export const Route = createFileRoute('/posts')({
loader: fetchPosts,
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ const $$splitComponentImporter = () => import('importAttribute.tsx?tsr-split=com
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
import { importedLoader } from '../../shared/imported';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr),
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url),
loader: importedLoader
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ const $$splitComponentImporter = () => import('imported-default-component.tsx?ts
import { lazyRouteComponent } from '@tanstack/react-router';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr)
component: lazyRouteComponent($$splitComponentImporter, 'component', () => Route.ssr, import.meta.url)
});
Loading