Skip to content

Support RSC lazy default export as Component fallback #13763

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2025
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
2 changes: 1 addition & 1 deletion integration/helpers/rsc-parcel/src/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function Component() {
export default function HomeRoute() {
return <h2>Home</h2>;
}
22 changes: 11 additions & 11 deletions integration/rsc/rsc-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ implementations.forEach((implementation) => {
export function loader() {
return { message: "Loader Data" };
}
export function Component({ loaderData }) {
export default function HomeRoute({ loaderData }) {
return <h2 data-home>Home: {loaderData.message}</h2>;
}
`,
Expand Down Expand Up @@ -141,7 +141,7 @@ implementations.forEach((implementation) => {
return { message: "Loader Data" };
}

export function Component({ loaderData }) {
export default function HomeRoute({ loaderData }) {
return (
<div>
<h2 data-home>Home: {loaderData.message}</h2>
Expand Down Expand Up @@ -236,7 +236,7 @@ implementations.forEach((implementation) => {
return { message: "Home Page Data" };
}

export function Component({ loaderData }) {
export default function HomeRoute({ loaderData }) {
return (
<div>
<h1 data-page="home">Home Page</h1>
Expand All @@ -251,7 +251,7 @@ implementations.forEach((implementation) => {
return { count: 1 };
}

export { Component } from "./dashboard.client";
export { default } from "./dashboard.client";
`,
"src/routes/dashboard.client.tsx": js`
"use client";
Expand All @@ -260,7 +260,7 @@ implementations.forEach((implementation) => {
import { Link } from "react-router";

// Export the entire route as a client component
export function Component({ loaderData }) {
export default function DashboardRoute({ loaderData }) {
const [count, setCount] = useState(loaderData.count);

return (
Expand Down Expand Up @@ -367,7 +367,7 @@ implementations.forEach((implementation) => {
return { message: "Home Page Data" };
}

export function Component({ loaderData }) {
export default function HomeRoute({ loaderData }) {
return (
<div>
<h1 data-page="home">Home Page</h1>
Expand Down Expand Up @@ -477,7 +477,7 @@ implementations.forEach((implementation) => {
}
`,
"src/routes/home.tsx": js`
export { Component } from "./home.client";
export { default } from "./home.client";
`,
"src/routes/home.client.tsx": js`
"use client";
Expand All @@ -486,7 +486,7 @@ implementations.forEach((implementation) => {

import { incrementCounter } from "./home.actions";

export function Component() {
export default function HomeRoute() {
const [count, incrementCounterAction, incrementing] = useActionState(incrementCounter, 0);

return (
Expand Down Expand Up @@ -547,7 +547,7 @@ implementations.forEach((implementation) => {
return { name, count };
}

export function Component({ loaderData }) {
export default function HomeRoute({ loaderData }) {
const updateCounter = async (formData: FormData) => {
"use server";
name = formData.get("name");
Expand Down Expand Up @@ -624,7 +624,7 @@ implementations.forEach((implementation) => {
import { redirectAction } from "./home.actions";
import { Counter } from "./home.client";

export function Component(props) {
export default function HomeRoute(props) {
console.log({props});
return (
<div>
Expand Down Expand Up @@ -697,7 +697,7 @@ implementations.forEach((implementation) => {
throw new Error("Intentional error from loader");
}

export function Component() {
export default function HomeRoute() {
return <h2>This shouldn't render</h2>;
}

Expand Down
51 changes: 35 additions & 16 deletions packages/react-router/lib/rsc/server.rsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ type ServerRouteObjectBase = {
action?: ActionFunction;
clientAction?: ClientActionFunction;
clientLoader?: ClientLoaderFunction;
Component?: React.ComponentType<any>;
ErrorBoundary?: React.ComponentType<any>;
handle?: any;
headers?: HeadersFunction;
Expand All @@ -48,7 +47,20 @@ type ServerRouteObjectBase = {
export type ServerRouteObject = ServerRouteObjectBase & {
id: string;
path?: string;
lazy?: () => Promise<ServerRouteObjectBase>;
Component?: React.ComponentType<any>;
lazy?: () => Promise<
ServerRouteObjectBase &
(
| {
default?: React.ComponentType<any>;
Component?: never;
}
| {
default?: never;
Component?: React.ComponentType<any>;
}
)
>;
} & (
| {
index: true;
Expand Down Expand Up @@ -578,17 +590,8 @@ async function getServerRouteMatch(
shouldRenderComponent: boolean,
parentId: string | undefined
) {
if ("lazy" in match.route && match.route.lazy) {
Object.assign(match.route, {
// @ts-expect-error - FIXME: Fix the types here
...((await match.route.lazy()) as any),
path: match.route.path,
index: (match.route as any).index,
id: match.route.id,
});
match.route.lazy = undefined;
}

// @ts-expect-error - FIXME: Fix the types here
await explodeLazyRoute(match.route);
const Layout = (match.route as any).Layout || React.Fragment;
const Component = (match.route as any).Component;
const ErrorBoundary = (match.route as any).ErrorBoundary;
Expand Down Expand Up @@ -712,9 +715,25 @@ async function getManifestRoute(

async function explodeLazyRoute(route: ServerRouteObject) {
if ("lazy" in route && route.lazy) {
let impl = (await route.lazy()) as any;
for (let [k, v] of Object.entries(impl)) {
route[k as keyof ServerRouteObject] = v;
let {
default: lazyDefaultExport,
Component: lazyComponentExport,
...lazyProperties
} = (await route.lazy()) as any;
let Component = lazyComponentExport || lazyDefaultExport;
if (Component && !route.Component) {
route.Component = Component;
}
for (let [k, v] of Object.entries(lazyProperties)) {
if (
k !== "id" &&
k !== "path" &&
k !== "index" &&
k !== "children" &&
route[k as keyof ServerRouteObject] == null
) {
route[k as keyof ServerRouteObject] = v;
}
}
route.lazy = undefined;
}
Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-parcel/src/routes/about/about.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) {
};
}

export function Component() {
export default function AboutRoute() {
const { client, message } = useLoaderData<typeof clientLoader>();

return (
Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-parcel/src/routes/about/about.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { clientLoader, Component } from "./about.client";
export { clientLoader, default } from "./about.client";

export function loader() {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useFetcher } from "react-router";
import type { loader } from "../resource/resource";

export function Component() {
export default function FetcherRoute() {
const fetcher = useFetcher<typeof loader>();

return (
Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-parcel/src/routes/fetcher/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { Component } from "./fetcher.client";
export { default } from "./fetcher.client";
2 changes: 1 addition & 1 deletion playground/rsc-parcel/src/routes/home/home.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) {
};
}

export function Component() {
export default function HomeRoute() {
const { client, message } = useLoaderData<typeof clientLoader>();

return (
Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-parcel/src/routes/home/home.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { clientLoader, Component } from "./home.client";
export { clientLoader, default } from "./home.client";

export function loader() {
return {
Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-parcel/src/routes/root/root.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
);
}

export function Component() {
export default function RootRoute() {
const { counter, message } = useLoaderData<typeof loader>();
return (
<>
Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-parcel/src/routes/root/root.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { Component, ErrorBoundary, Layout } from "./root.client";
export { default, ErrorBoundary, Layout } from "./root.client";

import { Counter } from "../../counter";

Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-vite/src/routes/about/about.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function clientLoader({ serverLoader }: ClientLoaderFunctionArgs) {

clientLoader.hydrate = true;

export function Component() {
export default function AboutRoute() {
const loaderData = useLoaderData<typeof clientLoader>();
const actionData = useActionData<typeof clientAction>();

Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-vite/src/routes/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
};
}

export function Component({
export default function HomeRoute({
loaderData: { message, wasRedirected },
}: {
loaderData: Awaited<ReturnType<typeof loader>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export async function loader() {
};
}

export function Component({
export default function ParentIndexRoute({
loaderData,
}: {
loaderData: Awaited<ReturnType<typeof loader>>;
Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-vite/src/routes/parent/parent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function loader() {
};
}

export function Component({
export default function ParentRoute({
loaderData,
}: {
loaderData: Awaited<ReturnType<typeof loader>>;
Expand Down
2 changes: 1 addition & 1 deletion playground/rsc-vite/src/routes/root/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export async function loader() {
};
}

export function Component({
export default function RootRoute({
loaderData,
}: {
loaderData: Awaited<ReturnType<typeof loader>>;
Expand Down
Loading