diff --git a/packages/example-fs-routing/package.json b/packages/example-fs-routing/package.json
new file mode 100644
index 0000000..02f26b8
--- /dev/null
+++ b/packages/example-fs-routing/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "funstack-static-example-fs-routing",
+ "version": "0.0.0",
+ "private": true,
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@funstack/router": "^1.1.0",
+ "@funstack/static": "workspace:*",
+ "@types/node": "catalog:",
+ "react": "catalog:",
+ "react-dom": "catalog:"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "catalog:",
+ "vite": "catalog:"
+ }
+}
diff --git a/packages/example-fs-routing/src/App.tsx b/packages/example-fs-routing/src/App.tsx
new file mode 100644
index 0000000..e904c34
--- /dev/null
+++ b/packages/example-fs-routing/src/App.tsx
@@ -0,0 +1,6 @@
+import { Router } from "@funstack/router";
+import { routes } from "./routes";
+
+export default function App({ ssrPath }: { ssrPath: string }) {
+ return ;
+}
diff --git a/packages/example-fs-routing/src/entries.tsx b/packages/example-fs-routing/src/entries.tsx
new file mode 100644
index 0000000..0a54ad3
--- /dev/null
+++ b/packages/example-fs-routing/src/entries.tsx
@@ -0,0 +1,29 @@
+import type { EntryDefinition } from "@funstack/static/entries";
+import type { RouteDefinition } from "@funstack/router/server";
+import App from "./App";
+import { routes } from "./routes";
+
+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: ,
+ }));
+}
diff --git a/packages/example-fs-routing/src/index.css b/packages/example-fs-routing/src/index.css
new file mode 100644
index 0000000..c49d528
--- /dev/null
+++ b/packages/example-fs-routing/src/index.css
@@ -0,0 +1,55 @@
+:root {
+ font-family:
+ system-ui,
+ -apple-system,
+ sans-serif;
+ line-height: 1.6;
+ color: #213547;
+ background-color: #ffffff;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ color: #ffffffde;
+ background-color: #242424;
+ }
+
+ a {
+ color: #6db3f2;
+ }
+}
+
+body {
+ max-width: 720px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+nav {
+ padding-bottom: 1rem;
+ margin-bottom: 2rem;
+ border-bottom: 1px solid #ddd;
+}
+
+@media (prefers-color-scheme: dark) {
+ nav {
+ border-bottom-color: #444;
+ }
+}
+
+nav a {
+ margin: 0 0.25rem;
+}
+
+code {
+ background: #f4f4f4;
+ padding: 0.15em 0.3em;
+ border-radius: 3px;
+ font-size: 0.9em;
+}
+
+@media (prefers-color-scheme: dark) {
+ code {
+ background: #333;
+ }
+}
diff --git a/packages/example-fs-routing/src/pages/about.tsx b/packages/example-fs-routing/src/pages/about.tsx
new file mode 100644
index 0000000..055b02b
--- /dev/null
+++ b/packages/example-fs-routing/src/pages/about.tsx
@@ -0,0 +1,16 @@
+export default function About() {
+ return (
+
+
About
+
+ This example demonstrates file-system routing with{" "}
+ FUNSTACK Static .
+
+
+ Routes are derived from the file structure under src/pages/{" "}
+ using Vite's import.meta.glob, which also enables hot
+ module replacement during development.
+
+
+ );
+}
diff --git a/packages/example-fs-routing/src/pages/blog/index.tsx b/packages/example-fs-routing/src/pages/blog/index.tsx
new file mode 100644
index 0000000..03f048a
--- /dev/null
+++ b/packages/example-fs-routing/src/pages/blog/index.tsx
@@ -0,0 +1,15 @@
+export default function Blog() {
+ return (
+
+
Blog
+
+ This page is at pages/blog/index.tsx, which maps to the{" "}
+ /blog route.
+
+
+ Nested directories create nested URL paths. An index.tsx{" "}
+ file in a directory maps to the directory's path.
+
+
+ );
+}
diff --git a/packages/example-fs-routing/src/pages/index.tsx b/packages/example-fs-routing/src/pages/index.tsx
new file mode 100644
index 0000000..3558f66
--- /dev/null
+++ b/packages/example-fs-routing/src/pages/index.tsx
@@ -0,0 +1,28 @@
+export default function Home() {
+ return (
+
+
Home
+
+ Welcome to the file-system routing example! Pages in{" "}
+ src/pages/ are automatically mapped to routes using{" "}
+ import.meta.glob.
+
+
How it works
+
+
+ pages/index.tsx → /
+
+
+ pages/about.tsx → /about
+
+
+ pages/blog/index.tsx → /blog
+
+
+
+ Add a new .tsx file in the pages/ directory
+ and it will be automatically discovered as a new route.
+
+
+ );
+}
diff --git a/packages/example-fs-routing/src/root.tsx b/packages/example-fs-routing/src/root.tsx
new file mode 100644
index 0000000..90a5a40
--- /dev/null
+++ b/packages/example-fs-routing/src/root.tsx
@@ -0,0 +1,21 @@
+import type React from "react";
+import "./index.css";
+
+export default function Root({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+ FUNSTACK Static - File-System Routing
+
+
+
+ Home | About |{" "}
+ Blog
+
+ {children}
+
+
+ );
+}
diff --git a/packages/example-fs-routing/src/routes.tsx b/packages/example-fs-routing/src/routes.tsx
new file mode 100644
index 0000000..ff25660
--- /dev/null
+++ b/packages/example-fs-routing/src/routes.tsx
@@ -0,0 +1,24 @@
+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: ,
+ });
+ },
+);
diff --git a/packages/example-fs-routing/tsconfig.json b/packages/example-fs-routing/tsconfig.json
new file mode 100644
index 0000000..bb5d780
--- /dev/null
+++ b/packages/example-fs-routing/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "erasableSyntaxOnly": true,
+ "allowImportingTsExtensions": true,
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "skipLibCheck": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+ "moduleResolution": "Bundler",
+ "module": "ESNext",
+ "target": "ESNext",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "types": ["vite/client"],
+ "jsx": "react-jsx"
+ }
+}
diff --git a/packages/example-fs-routing/vite.config.ts b/packages/example-fs-routing/vite.config.ts
new file mode 100644
index 0000000..e192f96
--- /dev/null
+++ b/packages/example-fs-routing/vite.config.ts
@@ -0,0 +1,12 @@
+import funstackStatic from "@funstack/static";
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [
+ funstackStatic({
+ entries: "./src/entries.tsx",
+ }),
+ react(),
+ ],
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1eb9c08..cfbbf30 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -122,6 +122,37 @@ importers:
specifier: 'catalog:'
version: 8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)(tsx@4.21.0)
+ packages/example-fs-routing:
+ dependencies:
+ '@funstack/router':
+ specifier: ^1.1.0
+ version: 1.1.0(react@19.2.4)
+ '@funstack/static':
+ specifier: workspace:*
+ version: link:../static
+ '@types/node':
+ specifier: 'catalog:'
+ version: 25.5.0
+ react:
+ specifier: 'catalog:'
+ version: 19.2.4
+ react-dom:
+ specifier: 'catalog:'
+ version: 19.2.4(react@19.2.4)
+ devDependencies:
+ '@types/react':
+ specifier: ^19.2.14
+ version: 19.2.14
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.14)
+ '@vitejs/plugin-react':
+ specifier: 'catalog:'
+ version: 6.0.1(vite@8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)(tsx@4.21.0))
+ vite:
+ specifier: 'catalog:'
+ version: 8.0.1(@types/node@25.5.0)(esbuild@0.27.4)(terser@5.46.1)(tsx@4.21.0)
+
packages/static:
dependencies:
'@funstack/skill-installer':