Skip to content

Commit 1da30be

Browse files
committed
fix(start): align Rsbuild SSR asset URLs for css?url imports
1 parent 6f1daf5 commit 1da30be

11 files changed

Lines changed: 440 additions & 32 deletions

File tree

.changeset/fair-assets-align.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/start-plugin-core': patch
3+
---
4+
5+
Fix Rsbuild SSR asset URLs for `?url` imports by aligning server public asset paths with the client build.

e2e/react-start/rsc-rsbuild/src/node_modules/rsc-client-pkg/index.js

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/react-start/rsc-rsbuild/src/routeTree.gen.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010

1111
import { Route as rootRouteImport } from './routes/__root'
1212
import { Route as RscNodeModuleClientRouteImport } from './routes/rsc-node-module-client'
13+
import { Route as RscCssUrlRouteImport } from './routes/rsc-css-url'
1314
import { Route as IndexRouteImport } from './routes/index'
1415

1516
const RscNodeModuleClientRoute = RscNodeModuleClientRouteImport.update({
1617
id: '/rsc-node-module-client',
1718
path: '/rsc-node-module-client',
1819
getParentRoute: () => rootRouteImport,
1920
} as any)
21+
const RscCssUrlRoute = RscCssUrlRouteImport.update({
22+
id: '/rsc-css-url',
23+
path: '/rsc-css-url',
24+
getParentRoute: () => rootRouteImport,
25+
} as any)
2026
const IndexRoute = IndexRouteImport.update({
2127
id: '/',
2228
path: '/',
@@ -25,27 +31,31 @@ const IndexRoute = IndexRouteImport.update({
2531

2632
export interface FileRoutesByFullPath {
2733
'/': typeof IndexRoute
34+
'/rsc-css-url': typeof RscCssUrlRoute
2835
'/rsc-node-module-client': typeof RscNodeModuleClientRoute
2936
}
3037
export interface FileRoutesByTo {
3138
'/': typeof IndexRoute
39+
'/rsc-css-url': typeof RscCssUrlRoute
3240
'/rsc-node-module-client': typeof RscNodeModuleClientRoute
3341
}
3442
export interface FileRoutesById {
3543
__root__: typeof rootRouteImport
3644
'/': typeof IndexRoute
45+
'/rsc-css-url': typeof RscCssUrlRoute
3746
'/rsc-node-module-client': typeof RscNodeModuleClientRoute
3847
}
3948
export interface FileRouteTypes {
4049
fileRoutesByFullPath: FileRoutesByFullPath
41-
fullPaths: '/' | '/rsc-node-module-client'
50+
fullPaths: '/' | '/rsc-css-url' | '/rsc-node-module-client'
4251
fileRoutesByTo: FileRoutesByTo
43-
to: '/' | '/rsc-node-module-client'
44-
id: '__root__' | '/' | '/rsc-node-module-client'
52+
to: '/' | '/rsc-css-url' | '/rsc-node-module-client'
53+
id: '__root__' | '/' | '/rsc-css-url' | '/rsc-node-module-client'
4554
fileRoutesById: FileRoutesById
4655
}
4756
export interface RootRouteChildren {
4857
IndexRoute: typeof IndexRoute
58+
RscCssUrlRoute: typeof RscCssUrlRoute
4959
RscNodeModuleClientRoute: typeof RscNodeModuleClientRoute
5060
}
5161

@@ -58,6 +68,13 @@ declare module '@tanstack/react-router' {
5868
preLoaderRoute: typeof RscNodeModuleClientRouteImport
5969
parentRoute: typeof rootRouteImport
6070
}
71+
'/rsc-css-url': {
72+
id: '/rsc-css-url'
73+
path: '/rsc-css-url'
74+
fullPath: '/rsc-css-url'
75+
preLoaderRoute: typeof RscCssUrlRouteImport
76+
parentRoute: typeof rootRouteImport
77+
}
6178
'/': {
6279
id: '/'
6380
path: '/'
@@ -70,6 +87,7 @@ declare module '@tanstack/react-router' {
7087

7188
const rootRouteChildren: RootRouteChildren = {
7289
IndexRoute: IndexRoute,
90+
RscCssUrlRoute: RscCssUrlRoute,
7391
RscNodeModuleClientRoute: RscNodeModuleClientRoute,
7492
}
7593
export const routeTree = rootRouteImport
Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,160 @@
1-
import { Link, createFileRoute } from '@tanstack/react-router'
1+
import { createFileRoute, Link, linkOptions } from '@tanstack/react-router'
2+
import type { CSSProperties } from 'react'
23

34
export const Route = createFileRoute('/')({
45
component: Home,
56
})
67

8+
const examples = linkOptions([
9+
{
10+
to: '/rsc-node-module-client',
11+
title: 'Node module client component',
12+
description:
13+
'Hydrates a client component imported from node_modules inside an RSC route.',
14+
marker: 'RSC',
15+
markerColor: '#0284c7',
16+
},
17+
{
18+
to: '/rsc-css-url',
19+
title: 'css?url stylesheet',
20+
description:
21+
'Applies a stylesheet imported with the ?url query from route head metadata.',
22+
marker: 'CSS',
23+
markerColor: '#16a34a',
24+
},
25+
])
26+
27+
const colors = {
28+
server: '#0284c7',
29+
client: '#16a34a',
30+
async: '#f59e0b',
31+
}
32+
33+
const styles = {
34+
page: {
35+
maxWidth: '800px',
36+
padding: '20px',
37+
fontFamily:
38+
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
39+
},
40+
title: {
41+
margin: '0 0 8px 0',
42+
color: '#1e293b',
43+
fontSize: '24px',
44+
},
45+
description: {
46+
color: '#64748b',
47+
lineHeight: '1.5',
48+
marginBottom: '20px',
49+
},
50+
legend: {
51+
display: 'flex',
52+
gap: '16px',
53+
marginBottom: '20px',
54+
padding: '12px',
55+
backgroundColor: '#f8fafc',
56+
borderRadius: '8px',
57+
flexWrap: 'wrap',
58+
},
59+
legendItem: {
60+
display: 'flex',
61+
alignItems: 'center',
62+
gap: '6px',
63+
},
64+
legendText: {
65+
color: '#475569',
66+
fontSize: '13px',
67+
},
68+
legendColor: {
69+
width: '16px',
70+
height: '16px',
71+
borderRadius: '4px',
72+
},
73+
grid: {
74+
display: 'grid',
75+
gap: '16px',
76+
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
77+
},
78+
card: {
79+
display: 'block',
80+
padding: '16px',
81+
backgroundColor: '#f8fafc',
82+
borderRadius: '8px',
83+
border: '1px solid #e2e8f0',
84+
textDecoration: 'none',
85+
},
86+
marker: {
87+
display: 'grid',
88+
placeItems: 'center',
89+
width: '40px',
90+
height: '32px',
91+
marginBottom: '8px',
92+
borderRadius: '6px',
93+
color: 'white',
94+
fontSize: '13px',
95+
fontWeight: 'bold',
96+
},
97+
cardTitle: {
98+
marginBottom: '4px',
99+
color: '#0f172a',
100+
fontWeight: 'bold',
101+
},
102+
cardDescription: {
103+
color: '#64748b',
104+
fontSize: '13px',
105+
lineHeight: '1.4',
106+
},
107+
} satisfies Record<string, CSSProperties>
108+
109+
function legendColor(color: string): CSSProperties {
110+
return {
111+
...styles.legendColor,
112+
backgroundColor: color,
113+
}
114+
}
115+
116+
function markerStyle(color: string): CSSProperties {
117+
return {
118+
...styles.marker,
119+
backgroundColor: color,
120+
}
121+
}
122+
7123
function Home() {
8124
return (
9-
<main>
10-
<h1>Rsbuild RSC fixture</h1>
11-
<Link to="/rsc-node-module-client">Node module client component</Link>
125+
<main style={styles.page} data-testid="index-page">
126+
<h1 data-testid="home-title" style={styles.title}>
127+
React Server Components Rsbuild E2E Tests
128+
</h1>
129+
<p style={styles.description}>
130+
These examples cover Rsbuild-specific RSC behavior with clear visual
131+
distinction between server-rendered content and client-side assets.
132+
</p>
133+
134+
<div style={styles.legend}>
135+
<div style={styles.legendItem}>
136+
<span style={legendColor(colors.server)} />
137+
<span style={styles.legendText}>Server Rendered (RSC)</span>
138+
</div>
139+
<div style={styles.legendItem}>
140+
<span style={legendColor(colors.client)} />
141+
<span style={styles.legendText}>Client Hydration and Assets</span>
142+
</div>
143+
<div style={styles.legendItem}>
144+
<span style={legendColor(colors.async)} />
145+
<span style={styles.legendText}>Rsbuild SSR Coverage</span>
146+
</div>
147+
</div>
148+
149+
<nav style={styles.grid}>
150+
{examples.map(({ marker, markerColor, title, description, ...link }) => (
151+
<Link key={link.to} {...link} style={styles.card}>
152+
<div style={markerStyle(markerColor)}>{marker}</div>
153+
<div style={styles.cardTitle}>{title}</div>
154+
<div style={styles.cardDescription}>{description}</div>
155+
</Link>
156+
))}
157+
</nav>
12158
</main>
13159
)
14160
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.rsc-css-url-card {
2+
display: grid;
3+
gap: 12px;
4+
max-width: 480px;
5+
padding: 18px;
6+
background-color: #ecfdf5;
7+
border: 2px solid #10b981;
8+
border-radius: 8px;
9+
color: #064e3b;
10+
}
11+
12+
.rsc-css-url-badge {
13+
width: max-content;
14+
padding: 3px 8px;
15+
background-color: #047857;
16+
border-radius: 4px;
17+
color: #fff;
18+
font-size: 11px;
19+
font-weight: 700;
20+
}
21+
22+
.rsc-css-url-title {
23+
margin: 0;
24+
font-size: 18px;
25+
}
26+
27+
.rsc-css-url-text {
28+
margin: 0;
29+
line-height: 1.5;
30+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createFileRoute } from '@tanstack/react-router'
2+
import cssUrl from './rsc-css-url.css?url'
3+
4+
export const Route = createFileRoute('/rsc-css-url')({
5+
head: () => ({
6+
links: [{ rel: 'stylesheet', href: cssUrl }],
7+
}),
8+
component: RscCssUrlComponent,
9+
})
10+
11+
function RscCssUrlComponent() {
12+
return (
13+
<main>
14+
<h1 data-testid="rsc-css-url-title">RSC css?url stylesheet</h1>
15+
<section className="rsc-css-url-card" data-testid="rsc-css-url-card">
16+
<span className="rsc-css-url-badge">CSS URL</span>
17+
<h2 className="rsc-css-url-title">Stylesheet imported with ?url</h2>
18+
<p className="rsc-css-url-text">
19+
The SSR head should point at a client-served asset URL.
20+
</p>
21+
</section>
22+
</main>
23+
)
24+
}

0 commit comments

Comments
 (0)