Skip to content

Commit 3250231

Browse files
authored
fix(rsc): use req.originalUrl for server handler (#797)
1 parent 907b9d8 commit 3250231

File tree

4 files changed

+39
-15
lines changed

4 files changed

+39
-15
lines changed

packages/plugin-rsc/e2e/base.test.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { test } from '@playwright/test'
2-
import { setupInlineFixture, useFixture } from './fixture'
1+
import { expect, test } from '@playwright/test'
2+
import { setupInlineFixture, useFixture, type Fixture } from './fixture'
33
import { defineStarterTest } from './starter'
44

55
test.describe(() => {
@@ -27,17 +27,31 @@ test.describe(() => {
2727

2828
test.describe('dev-base', () => {
2929
const f = useFixture({ root, mode: 'dev' })
30-
defineStarterTest({
30+
const f2: Fixture = {
3131
...f,
3232
url: (url) => new URL(url ?? './', f.url('./custom-base/')).href,
33-
})
33+
}
34+
defineStarterTest(f2)
35+
testRequestUrl(f2)
3436
})
3537

3638
test.describe('build-base', () => {
3739
const f = useFixture({ root, mode: 'build' })
38-
defineStarterTest({
40+
const f2: Fixture = {
3941
...f,
4042
url: (url) => new URL(url ?? './', f.url('./custom-base/')).href,
41-
})
43+
}
44+
defineStarterTest(f2)
45+
testRequestUrl(f2)
4246
})
47+
48+
function testRequestUrl(f: Fixture) {
49+
test('request url', async ({ page }) => {
50+
await page.goto(f.url())
51+
await page.waitForSelector('#root')
52+
await expect(page.locator('.card').nth(2)).toHaveText(
53+
`Request URL: ${f.url()}`,
54+
)
55+
})
56+
}
4357
})

packages/plugin-rsc/examples/starter/src/framework/entry.rsc.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,18 @@ export default async function handler(request: Request): Promise<Response> {
5858
// we render RSC stream after handling server function request
5959
// so that new render reflects updated state from server function call
6060
// to achieve single round trip to mutate and fetch from server.
61-
const rscPayload: RscPayload = { root: <Root />, formState, returnValue }
61+
const url = new URL(request.url)
62+
const rscPayload: RscPayload = {
63+
root: <Root url={url} />,
64+
formState,
65+
returnValue,
66+
}
6267
const rscOptions = { temporaryReferences }
6368
const rscStream = renderToReadableStream<RscPayload>(rscPayload, rscOptions)
6469

6570
// respond RSC stream without HTML rendering based on framework's convention.
6671
// here we use request header `content-type`.
6772
// additionally we allow `?__rsc` and `?__html` to easily view payload directly.
68-
const url = new URL(request.url)
6973
const isRscRequest =
7074
(!request.headers.get('accept')?.includes('text/html') &&
7175
!url.searchParams.has('__html')) ||

packages/plugin-rsc/examples/starter/src/root.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getServerCounter, updateServerCounter } from './action.tsx'
44
import reactLogo from './assets/react.svg'
55
import { ClientCounter } from './client.tsx'
66

7-
export function Root() {
7+
export function Root(props: { url: URL }) {
88
return (
99
<html lang="en">
1010
<head>
@@ -14,13 +14,13 @@ export function Root() {
1414
<title>Vite + RSC</title>
1515
</head>
1616
<body>
17-
<App />
17+
<App {...props} />
1818
</body>
1919
</html>
2020
)
2121
}
2222

23-
function App() {
23+
function App(props: { url: URL }) {
2424
return (
2525
<div id="root">
2626
<div>
@@ -43,6 +43,7 @@ function App() {
4343
<button>Server Counter: {getServerCounter()}</button>
4444
</form>
4545
</div>
46+
<div className="card">Request URL: {props.url?.href}</div>
4647
<ul className="read-the-docs">
4748
<li>
4849
Edit <code>src/client.tsx</code> to test client HMR.
@@ -52,15 +53,15 @@ function App() {
5253
</li>
5354
<li>
5455
Visit{' '}
55-
<a href="/?__rsc" target="_blank">
56-
<code>/?__rsc</code>
56+
<a href="?__rsc" target="_blank">
57+
<code>?__rsc</code>
5758
</a>{' '}
5859
to view RSC stream payload.
5960
</li>
6061
<li>
6162
Visit{' '}
62-
<a href="/?__nojs" target="_blank">
63-
<code>/?__nojs</code>
63+
<a href="?__nojs" target="_blank">
64+
<code>?__nojs</code>
6465
</a>{' '}
6566
to test server action without js enabled.
6667
</li>

packages/plugin-rsc/src/plugin.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,10 @@ export default function vitePluginRsc(
471471
`[vite-rsc] failed to resolve server handler '${source}'`,
472472
)
473473
const mod = await environment.runner.import(resolved.id)
474+
// expose original request url to server handler.
475+
// for example, this restores `base` which is automatically stripped by Vite.
476+
// https://github.com/vitejs/vite/blob/84079a84ad94de4c1ef4f1bdb2ab448ff2c01196/packages/vite/src/node/server/middlewares/base.ts#L18-L20
477+
req.url = req.originalUrl ?? req.url
474478
// ensure catching rejected promise
475479
// https://github.com/mjackson/remix-the-web/blob/b5aa2ae24558f5d926af576482caf6e9b35461dc/packages/node-fetch-server/src/lib/request-listener.ts#L87
476480
await createRequestListener(mod.default)(req, res)
@@ -506,6 +510,7 @@ export default function vitePluginRsc(
506510
return () => {
507511
server.middlewares.use(async (req, res, next) => {
508512
try {
513+
req.url = req.originalUrl ?? req.url
509514
await handler(req, res)
510515
} catch (e) {
511516
next(e)

0 commit comments

Comments
 (0)