Skip to content

Commit 9f19dcf

Browse files
authored
fix: respect basePath setting (#5)
* docs: use BASE_URL env * fix: support basePath setting of Cite * docs: update links
1 parent 7648a49 commit 9f19dcf

9 files changed

Lines changed: 92 additions & 37 deletions

File tree

packages/docs/src/App.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Router } from "@funstack/router";
1+
import { Outlet, Router } from "@funstack/router";
22
import { route, type RouteDefinition } from "@funstack/router/server";
33
import { defer } from "@funstack/static/server";
44
import { Layout } from "./components/Layout/Layout";
@@ -10,28 +10,34 @@ import { Home } from "./pages/Home";
1010

1111
const routes: RouteDefinition[] = [
1212
route({
13-
path: "/",
14-
component: (
15-
<Layout variant="home">
16-
<Home />
17-
</Layout>
18-
),
19-
}),
20-
route({
21-
path: "/getting-started",
22-
component: <Layout>{defer(GettingStarted)}</Layout>,
23-
}),
24-
route({
25-
path: "/api/funstack-static",
26-
component: <Layout>{defer(FunstackStaticApi)}</Layout>,
27-
}),
28-
route({
29-
path: "/api/defer",
30-
component: <Layout>{defer(DeferApi)}</Layout>,
31-
}),
32-
route({
33-
path: "/concepts/rsc",
34-
component: <Layout>{defer(RSCConcept)}</Layout>,
13+
path: import.meta.env.BASE_URL.replace(/\/$/, ""),
14+
component: <Outlet />,
15+
children: [
16+
route({
17+
path: "/",
18+
component: (
19+
<Layout variant="home">
20+
<Home />
21+
</Layout>
22+
),
23+
}),
24+
route({
25+
path: "/getting-started",
26+
component: <Layout>{defer(GettingStarted)}</Layout>,
27+
}),
28+
route({
29+
path: "/api/funstack-static",
30+
component: <Layout>{defer(FunstackStaticApi)}</Layout>,
31+
}),
32+
route({
33+
path: "/api/defer",
34+
component: <Layout>{defer(DeferApi)}</Layout>,
35+
}),
36+
route({
37+
path: "/concepts/rsc",
38+
component: <Layout>{defer(RSCConcept)}</Layout>,
39+
}),
40+
],
3541
}),
3642
];
3743

packages/docs/src/components/Header/Header.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,32 @@ export const Header: React.FC = () => {
44
return (
55
<header className={styles.header}>
66
<div className={styles.container}>
7-
<a href="/" className={styles.logo}>
7+
<a href={import.meta.env.BASE_URL} className={styles.logo}>
88
<span className={styles.logoIcon}>F</span>
99
<span className={styles.logoText}>FUNSTACK Static</span>
1010
</a>
1111

1212
<nav className={styles.nav}>
13-
<a href="/getting-started" className={styles.navLink}>
13+
<a
14+
href={`${import.meta.env.BASE_URL}getting-started`}
15+
className={styles.navLink}
16+
>
1417
Docs
1518
</a>
16-
<a href="/api/funstack-static" className={styles.navLink}>
19+
<a
20+
href={`${import.meta.env.BASE_URL}api/funstack-static`}
21+
className={styles.navLink}
22+
>
1723
API
1824
</a>
19-
<a href="/concepts/rsc" className={styles.navLink}>
25+
<a
26+
href={`${import.meta.env.BASE_URL}concepts/rsc`}
27+
className={styles.navLink}
28+
>
2029
Concepts
2130
</a>
2231
<a
23-
href="https://github.com/user/funstack-static"
32+
href="https://github.com/uhyo/funstack-static"
2433
target="_blank"
2534
rel="noopener noreferrer"
2635
className={styles.githubLink}

packages/docs/src/pages/Home.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ export const Home: React.FC = () => {
2727
with full SPA interactivity.
2828
</p>
2929
<div className={styles.buttons}>
30-
<a href="/getting-started" className={styles.primaryButton}>
30+
<a
31+
href={`${import.meta.env.BASE_URL}getting-started`}
32+
className={styles.primaryButton}
33+
>
3134
Get Started
3235
<svg
3336
width="20"
@@ -211,7 +214,10 @@ export const Home: React.FC = () => {
211214
Build your next SPA with the performance benefits of React Server
212215
Components.
213216
</p>
214-
<a href="/getting-started" className={styles.ctaButton}>
217+
<a
218+
href={`${import.meta.env.BASE_URL}getting-started`}
219+
className={styles.ctaButton}
220+
>
215221
Read the Documentation
216222
<svg
217223
width="20"

packages/docs/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ export default defineConfig({
2828
},
2929
react({ include: /\.(jsx|js|mdx|md|tsx|ts)$/ }),
3030
],
31+
base: "/funstack-static/",
3132
});

packages/static/src/client/entry.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { GlobalErrorBoundary } from "./error-boundary";
99
import type { RscPayload } from "../rsc/entry";
1010
import { devMainRscPath } from "../rsc/request";
1111
import { appClientManifestVar, type AppClientManifest } from "./globals";
12+
import { withBasePath } from "../util/basePath";
1213

1314
async function devMain() {
1415
let setPayload: (v: RscPayload) => void;
@@ -27,7 +28,9 @@ async function devMain() {
2728

2829
// re-fetch RSC and trigger re-rendering
2930
async function fetchRscPayload() {
30-
const payload = await createFromFetch<RscPayload>(fetch(devMainRscPath));
31+
const payload = await createFromFetch<RscPayload>(
32+
fetch(withBasePath(devMainRscPath)),
33+
);
3134
setPayload(payload);
3235
}
3336
// hydration

packages/static/src/rsc-client/clientWrapper.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
import { getModulePathFor } from "../rsc/rscModule";
77
import { createContext, use } from "react";
88
import type { LoadedDeferEntry, DeferRegistry } from "../rsc/defer";
9+
import { withBasePath } from "../util/basePath";
910

1011
export const RegistryContext = createContext<DeferRegistry | undefined>(
1112
undefined,
@@ -25,7 +26,7 @@ export const ClientWrapper: React.FC<ClientWrapperProps> = ({ moduleID }) => {
2526
}
2627
return getRSCStreamFromRegistry(entry);
2728
}
28-
const stream = getClientRSCStream(modulePath);
29+
const stream = getClientRSCStream(withBasePath(modulePath));
2930
return use(stream);
3031
};
3132

packages/static/src/rsc/entry.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { devMainRscPath } from "./request";
44
import { generateAppMarker } from "./marker";
55
import { deferRegistry } from "./defer";
66
import { extractIDFromModulePath } from "./rscModule";
7+
import { stripBasePath } from "../util/basePath";
78

89
export type RscPayload = {
910
root: React.ReactNode;
@@ -79,7 +80,8 @@ export function isServeRSCError(error: unknown): error is ServeRSCError {
7980
*/
8081
export async function serveRSC(request: Request): Promise<Response> {
8182
const url = new URL(request.url);
82-
if (url.pathname === devMainRscPath) {
83+
const pathname = stripBasePath(url.pathname);
84+
if (pathname === devMainRscPath) {
8385
// root RSC stream is requested
8486
const rootRscStream = await devMainRSCStream();
8587
return new Response(rootRscStream, {
@@ -90,9 +92,9 @@ export async function serveRSC(request: Request): Promise<Response> {
9092
});
9193
}
9294

93-
const moduleId = extractIDFromModulePath(url.pathname);
95+
const moduleId = extractIDFromModulePath(pathname);
9496
if (!moduleId) {
95-
throw new ServeRSCError(`Invalid RSC module path: ${url.pathname}`, 404);
97+
throw new ServeRSCError(`Invalid RSC module path: ${pathname}`, 404);
9698
}
9799

98100
const entry = deferRegistry.load(moduleId);

packages/static/src/ssr/entry.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { rscPayloadPath } from "../build/rscPath";
88
import { preload } from "react-dom";
99
import type { DeferRegistry } from "../rsc/defer";
1010
import { RegistryContext } from "#rsc-client";
11+
import { withBasePath } from "../util/basePath";
1112

1213
export async function renderHTML(
1314
rscStream: ReadableStream<Uint8Array>,
@@ -26,7 +27,7 @@ export async function renderHTML(
2627
// makes `preinit`/`preload` work properly.
2728
payload ??= createFromReadableStream<RscPayload>(rscStream1);
2829
if (options.build) {
29-
preload(rscPayloadPath, {
30+
preload(withBasePath(rscPayloadPath), {
3031
crossOrigin: "anonymous",
3132
as: "fetch",
3233
});
@@ -38,7 +39,7 @@ export async function renderHTML(
3839
);
3940
}
4041

41-
const builtRscUrl = rscPayloadPath;
42+
const builtRscUrl = withBasePath(rscPayloadPath);
4243
let bootstrapScriptContent: string = "";
4344
if (options.build) {
4445
bootstrapScriptContent += `globalThis.${appClientManifestVar}={marker:"${options.appEntryMarker}",stream:"${builtRscUrl}"};\n`;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Strips the base path from a pathname if present.
3+
* Used on server-side to normalize incoming request paths.
4+
*/
5+
export function stripBasePath(pathname: string): string {
6+
const base = import.meta.env.BASE_URL;
7+
if (base === "/") return pathname;
8+
// Handle base with or without trailing slash
9+
const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
10+
if (pathname.startsWith(normalizedBase)) {
11+
const stripped = pathname.slice(normalizedBase.length);
12+
return stripped.startsWith("/") ? stripped : "/" + stripped;
13+
}
14+
return pathname;
15+
}
16+
17+
/**
18+
* Prepends the base path to a path.
19+
* Used on client-side when making fetch requests.
20+
*/
21+
export function withBasePath(path: string): string {
22+
const base = import.meta.env.BASE_URL;
23+
if (base === "/") return path;
24+
const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
25+
return normalizedBase + path;
26+
}

0 commit comments

Comments
 (0)