diff --git a/packages/docs/src/App.tsx b/packages/docs/src/App.tsx
index 627cf6f..d2fe691 100644
--- a/packages/docs/src/App.tsx
+++ b/packages/docs/src/App.tsx
@@ -9,6 +9,7 @@ import LazyServerComponents from "./pages/learn/LazyServerComponents.mdx";
import OptimizingPayloads from "./pages/learn/OptimizingPayloads.mdx";
import RSCConcept from "./pages/learn/RSC.mdx";
import DeferAndActivity from "./pages/learn/DeferAndActivity.mdx";
+import FileSystemRouting from "./pages/learn/FileSystemRouting.mdx";
import MultipleEntrypoints from "./pages/advanced/MultipleEntrypoints.mdx";
import SSR from "./pages/advanced/SSR.mdx";
import EntryDefinitionApi from "./pages/api/EntryDefinition.mdx";
@@ -108,6 +109,14 @@ export const routes: RouteDefinition[] = [
),
}),
+ route({
+ path: "/learn/file-system-routing",
+ component: (
+
+ {defer(, { name: "FileSystemRouting" })}
+
+ ),
+ }),
route({
path: "/advanced/multiple-entrypoints",
component: (
diff --git a/packages/docs/src/components/Sidebar/Sidebar.tsx b/packages/docs/src/components/Sidebar/Sidebar.tsx
index 0eaf5fa..9d1a09d 100644
--- a/packages/docs/src/components/Sidebar/Sidebar.tsx
+++ b/packages/docs/src/components/Sidebar/Sidebar.tsx
@@ -44,6 +44,10 @@ export const navigation: NavSection[] = [
label: "Prefetching with Activity",
href: "/learn/defer-and-activity",
},
+ {
+ label: "File-System Routing",
+ href: "/learn/file-system-routing",
+ },
],
},
{
diff --git a/packages/docs/src/pages/learn/FileSystemRouting.mdx b/packages/docs/src/pages/learn/FileSystemRouting.mdx
new file mode 100644
index 0000000..fad9e33
--- /dev/null
+++ b/packages/docs/src/pages/learn/FileSystemRouting.mdx
@@ -0,0 +1,95 @@
+# File-System Routing
+
+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).
+
+## How It Works
+
+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.
+
+```tsx
+import { route, type RouteDefinition } from "@funstack/router/server";
+
+const pageModules = import.meta.glob<{ default: React.ComponentType }>(
+ "./pages/**/*.tsx",
+ { eager: true },
+);
+
+function filePathToUrlPath(filePath: string): string {
+ let urlPath = filePath.replace(/^\.\/pages/, "").replace(/\.tsx$/, "");
+ if (urlPath.endsWith("/index")) {
+ urlPath = urlPath.slice(0, -"/index".length);
+ }
+ return urlPath || "/";
+}
+
+export const routes: RouteDefinition[] = Object.entries(pageModules).map(
+ ([filePath, module]) => {
+ const Page = module.default;
+ return route({
+ path: filePathToUrlPath(filePath),
+ component: ,
+ });
+ },
+);
+```
+
+With this setup, files in the `pages/` directory are automatically mapped to routes:
+
+| File | Route |
+| ---------------------- | -------- |
+| `pages/index.tsx` | `/` |
+| `pages/about.tsx` | `/about` |
+| `pages/blog/index.tsx` | `/blog` |
+
+## Why import.meta.glob?
+
+Using `import.meta.glob` has two key advantages:
+
+- **Automatic discovery** — you don't need to manually register each page. Just add a new `.tsx` file and it becomes a route.
+- **Hot module replacement** — Vite tracks the glob pattern, so adding or removing page files in development triggers an automatic update without a server restart.
+
+## Static Generation
+
+To generate static HTML for each route, derive [entry definitions](/api/entry-definition) from the route list:
+
+```tsx
+import type { EntryDefinition } from "@funstack/static/entries";
+import type { RouteDefinition } from "@funstack/router/server";
+
+function collectPaths(routes: RouteDefinition[]): string[] {
+ const paths: string[] = [];
+ for (const route of routes) {
+ if (route.children) {
+ paths.push(...collectPaths(route.children));
+ } else if (route.path !== undefined && route.path !== "*") {
+ paths.push(route.path);
+ }
+ }
+ return paths;
+}
+
+function pathToEntryPath(path: string): string {
+ if (path === "/") return "index.html";
+ return `${path.slice(1)}.html`;
+}
+
+export default function getEntries(): EntryDefinition[] {
+ return collectPaths(routes).map((pathname) => ({
+ path: pathToEntryPath(pathname),
+ root: () => import("./root"),
+ app: ,
+ }));
+}
+```
+
+This produces one HTML file per route at build time.
+
+## Full Example
+
+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.
+
+## See Also
+
+- [Multiple Entrypoints](/advanced/multiple-entrypoints) - Generating multiple HTML pages from a single project
+- [EntryDefinition](/api/entry-definition) - API reference for entry definitions
+- [How It Works](/learn/how-it-works) - Overall FUNSTACK Static architecture