Skip to content
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
6 changes: 6 additions & 0 deletions .changeset/gorgeous-panthers-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@react-router/dev": patch
"react-router": patch
---

In (unstable) RSC Framework Mode, always keep the `ErrorBoundary`, `HydrateFallback` and `Layout` Route Module exports as client components, even when a `ServerComponent` export is present
6 changes: 3 additions & 3 deletions docs/how-to/react-server-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ The quickest way to get started is with one of our templates.

These templates come with React Router RSC APIs already configured, offering you out of the box features such as:

- Server Component Routes
- Server Side Rendering (SSR)
- Server Components
- Client Components (via [`"use client"`][use-client-docs] directive)
- Server Functions (via [`"use server"`][use-server-docs] directive)

Expand Down Expand Up @@ -177,9 +177,9 @@ export default function Route({
}
```

### Server Component Routes
### Route Server Components

If a route exports a `ServerComponent` instead of the typical `default` component export, this component along with other route components (`ErrorBoundary`, `HydrateFallback`, `Layout`) will be server components rather than the usual client components.
If a route exports a `ServerComponent` instead of the typical `default` component export, this will be a server component rather than the usual client component.

```tsx
import type { Route } from "./+types/route";
Expand Down
98 changes: 1 addition & 97 deletions integration/typegen-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ test.describe("typegen", () => {
});
});

test.describe("server-first route component detection", () => {
test.describe("route server component detection", () => {
test.describe("ServerComponent export", () => {
test("when RSC Framework Mode plugin is present", async ({ edit, $ }) => {
await edit({
Expand Down Expand Up @@ -706,38 +706,6 @@ test.describe("typegen", () => {
</>
)
}

export function ErrorBoundary({
loaderData,
actionData
}: Route.ErrorBoundaryProps) {
type TestLoaderData = Expect<Equal<typeof loaderData, { server: string } | undefined>>
type TestActionData = Expect<Equal<typeof actionData, { server: string } | undefined>>

return (
<>
<h1>ErrorBoundary</h1>
<p>Loader data: {loaderData?.server}</p>
<p>Action data: {actionData?.server}</p>
</>
)
}

export function HydrateFallback({
loaderData,
actionData
}: Route.HydrateFallbackProps) {
type TestLoaderData = Expect<Equal<typeof loaderData, { server: string } | undefined>>
type TestActionData = Expect<Equal<typeof actionData, { server: string } | undefined>>

return (
<>
<h1>HydrateFallback</h1>
<p>Loader data: {loaderData?.server}</p>
<p>Action data: {actionData?.server}</p>
</>
)
}
`,
});
await $("pnpm typecheck");
Expand Down Expand Up @@ -795,38 +763,6 @@ test.describe("typegen", () => {
</>
)
}

export function ErrorBoundary({
loaderData,
actionData
}: Route.ErrorBoundaryProps) {
type TestLoaderData = Expect<Equal<typeof loaderData, { server: string } | { client: string } | undefined>>
type TestActionData = Expect<Equal<typeof actionData, { server: string } | { client: string } | undefined>>

return (
<>
<h1>ErrorBoundary</h1>
{loaderData && <p>Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}</p>}
{actionData && <p>Action data: {"server" in actionData ? actionData.server : actionData.client}</p>}
</>
)
}

export function HydrateFallback({
loaderData,
actionData
}: Route.HydrateFallbackProps) {
type TestLoaderData = Expect<Equal<typeof loaderData, { server: string } | { client: string } | undefined>>
type TestActionData = Expect<Equal<typeof actionData, { server: string } | { client: string } | undefined>>

return (
<>
<h1>HydrateFallback</h1>
{loaderData && <p>Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}</p>}
{actionData && <p>Action data: {"server" in actionData ? actionData.server : actionData.client}</p>}
</>
)
}
`,
});
await $("pnpm typecheck");
Expand Down Expand Up @@ -878,38 +814,6 @@ test.describe("typegen", () => {
</>
)
}

export function ErrorBoundary({
loaderData,
actionData
}: Route.ErrorBoundaryProps) {
type TestLoaderData = Expect<Equal<typeof loaderData, { server: string } | { client: string } | undefined>>
type TestActionData = Expect<Equal<typeof actionData, { server: string } | { client: string } | undefined>>

return (
<>
<h1>ErrorBoundary</h1>
{loaderData && <p>Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}</p>}
{actionData && <p>Action data: {"server" in actionData ? actionData.server : actionData.client}</p>}
</>
)
}

export function HydrateFallback({
loaderData,
actionData
}: Route.HydrateFallbackProps) {
type TestLoaderData = Expect<Equal<typeof loaderData, { server: string } | { client: string } | undefined>>
type TestActionData = Expect<Equal<typeof actionData, { server: string } | { client: string } | undefined>>

return (
<>
<h1>HydrateFallback</h1>
{loaderData && <p>Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}</p>}
{actionData && <p>Action data: {"server" in actionData ? actionData.server : actionData.client}</p>}
</>
)
}
`,
};

Expand Down
Loading