Skip to content

Commit 8370de2

Browse files
uhyoclaude
andauthored
docs: add Learn page on file-system routing (#86)
Adds a new Learn page explaining how to implement file-system routing using import.meta.glob and @funstack/router, with a pointer to the example-fs-routing package for a complete working example. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 19e6a8e commit 8370de2

3 files changed

Lines changed: 108 additions & 0 deletions

File tree

packages/docs/src/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import LazyServerComponents from "./pages/learn/LazyServerComponents.mdx";
99
import OptimizingPayloads from "./pages/learn/OptimizingPayloads.mdx";
1010
import RSCConcept from "./pages/learn/RSC.mdx";
1111
import DeferAndActivity from "./pages/learn/DeferAndActivity.mdx";
12+
import FileSystemRouting from "./pages/learn/FileSystemRouting.mdx";
1213
import MultipleEntrypoints from "./pages/advanced/MultipleEntrypoints.mdx";
1314
import SSR from "./pages/advanced/SSR.mdx";
1415
import EntryDefinitionApi from "./pages/api/EntryDefinition.mdx";
@@ -108,6 +109,14 @@ export const routes: RouteDefinition[] = [
108109
</Layout>
109110
),
110111
}),
112+
route({
113+
path: "/learn/file-system-routing",
114+
component: (
115+
<Layout>
116+
{defer(<FileSystemRouting />, { name: "FileSystemRouting" })}
117+
</Layout>
118+
),
119+
}),
111120
route({
112121
path: "/advanced/multiple-entrypoints",
113122
component: (

packages/docs/src/components/Sidebar/Sidebar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export const navigation: NavSection[] = [
4444
label: "Prefetching with Activity",
4545
href: "/learn/defer-and-activity",
4646
},
47+
{
48+
label: "File-System Routing",
49+
href: "/learn/file-system-routing",
50+
},
4751
],
4852
},
4953
{
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# File-System Routing
2+
3+
FUNSTACK Static does not include a built-in file-system router, but you can implement one in userland using Vite's `import.meta.glob` and a router library like [FUNSTACK Router](https://github.com/uhyo/funstack-router).
4+
5+
## How It Works
6+
7+
The idea is to use `import.meta.glob` to discover page components from a `pages/` directory at compile time, then convert the file paths into route definitions.
8+
9+
```tsx
10+
import { route, type RouteDefinition } from "@funstack/router/server";
11+
12+
const pageModules = import.meta.glob<{ default: React.ComponentType }>(
13+
"./pages/**/*.tsx",
14+
{ eager: true },
15+
);
16+
17+
function filePathToUrlPath(filePath: string): string {
18+
let urlPath = filePath.replace(/^\.\/pages/, "").replace(/\.tsx$/, "");
19+
if (urlPath.endsWith("/index")) {
20+
urlPath = urlPath.slice(0, -"/index".length);
21+
}
22+
return urlPath || "/";
23+
}
24+
25+
export const routes: RouteDefinition[] = Object.entries(pageModules).map(
26+
([filePath, module]) => {
27+
const Page = module.default;
28+
return route({
29+
path: filePathToUrlPath(filePath),
30+
component: <Page />,
31+
});
32+
},
33+
);
34+
```
35+
36+
With this setup, files in the `pages/` directory are automatically mapped to routes:
37+
38+
| File | Route |
39+
| ---------------------- | -------- |
40+
| `pages/index.tsx` | `/` |
41+
| `pages/about.tsx` | `/about` |
42+
| `pages/blog/index.tsx` | `/blog` |
43+
44+
## Why import.meta.glob?
45+
46+
Using `import.meta.glob` has two key advantages:
47+
48+
- **Automatic discovery** — you don't need to manually register each page. Just add a new `.tsx` file and it becomes a route.
49+
- **Hot module replacement** — Vite tracks the glob pattern, so adding or removing page files in development triggers an automatic update without a server restart.
50+
51+
## Static Generation
52+
53+
To generate static HTML for each route, derive [entry definitions](/api/entry-definition) from the route list:
54+
55+
```tsx
56+
import type { EntryDefinition } from "@funstack/static/entries";
57+
import type { RouteDefinition } from "@funstack/router/server";
58+
59+
function collectPaths(routes: RouteDefinition[]): string[] {
60+
const paths: string[] = [];
61+
for (const route of routes) {
62+
if (route.children) {
63+
paths.push(...collectPaths(route.children));
64+
} else if (route.path !== undefined && route.path !== "*") {
65+
paths.push(route.path);
66+
}
67+
}
68+
return paths;
69+
}
70+
71+
function pathToEntryPath(path: string): string {
72+
if (path === "/") return "index.html";
73+
return `${path.slice(1)}.html`;
74+
}
75+
76+
export default function getEntries(): EntryDefinition[] {
77+
return collectPaths(routes).map((pathname) => ({
78+
path: pathToEntryPath(pathname),
79+
root: () => import("./root"),
80+
app: <App ssrPath={pathname} />,
81+
}));
82+
}
83+
```
84+
85+
This produces one HTML file per route at build time.
86+
87+
## Full Example
88+
89+
For a complete working example, see the [`example-fs-routing`](https://github.com/uhyo/funstack-static/tree/master/packages/example-fs-routing) package in the FUNSTACK Static repository.
90+
91+
## See Also
92+
93+
- [Multiple Entrypoints](/advanced/multiple-entrypoints) - Generating multiple HTML pages from a single project
94+
- [EntryDefinition](/api/entry-definition) - API reference for entry definitions
95+
- [How It Works](/learn/how-it-works) - Overall FUNSTACK Static architecture

0 commit comments

Comments
 (0)