diff --git a/packages/example-pathless-ssr/package.json b/packages/example-pathless-ssr/package.json
new file mode 100644
index 0000000..b1117b5
--- /dev/null
+++ b/packages/example-pathless-ssr/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "funstack-router-example-pathless-ssr",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@funstack/router": "workspace:*",
+ "@funstack/static": "^1.1.2",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.1",
+ "typescript": "^6.0.2",
+ "vite": "^8.0.3"
+ }
+}
diff --git a/packages/example-pathless-ssr/src/App.tsx b/packages/example-pathless-ssr/src/App.tsx
new file mode 100644
index 0000000..f1693c2
--- /dev/null
+++ b/packages/example-pathless-ssr/src/App.tsx
@@ -0,0 +1,53 @@
+import { bindRoute, route } from "@funstack/router/server";
+import { ClientApp } from "./ClientApp.js";
+import { Layout } from "./components/Layout.js";
+
+// Phase 1 route imports (shared modules — loaders/actions are client references)
+import { homeRoute } from "./features/home/route.js";
+import {
+ recipeListRoute,
+ recipeDetailRoute,
+ newRecipeRoute,
+} from "./features/recipes/route.js";
+import { favoritesRoute } from "./features/favorites/route.js";
+
+// Server component imports
+import { HomePage } from "./features/home/HomePage.js";
+import { RecipeListPage } from "./features/recipes/RecipeListPage.js";
+import { RecipeDetailPage } from "./features/recipes/RecipeDetailPage.js";
+import { NewRecipeForm } from "./features/recipes/NewRecipeForm.js";
+
+// Client component imports (passed as function reference to receive route props)
+import { FavoritesList } from "./features/favorites/FavoritesList.js";
+
+// Phase 2: Bind components to routes and assemble the route tree.
+// The root Layout is a pathless route — it has no `path` property, so it
+// always matches. During SSR (without the `ssr` prop), only this pathless
+// layout renders, producing an app shell. Path-based children render on
+// the client after hydration.
+export const routes = [
+ route({
+ component: ,
+ children: [
+ bindRoute(homeRoute, {
+ component: ,
+ }),
+ bindRoute(recipeListRoute, {
+ component: ,
+ }),
+ bindRoute(newRecipeRoute, {
+ component: ,
+ }),
+ bindRoute(recipeDetailRoute, {
+ component: ,
+ }),
+ bindRoute(favoritesRoute, {
+ component: FavoritesList,
+ }),
+ ],
+ }),
+];
+
+export default function App() {
+ return ;
+}
diff --git a/packages/example-pathless-ssr/src/ClientApp.tsx b/packages/example-pathless-ssr/src/ClientApp.tsx
new file mode 100644
index 0000000..928d53d
--- /dev/null
+++ b/packages/example-pathless-ssr/src/ClientApp.tsx
@@ -0,0 +1,10 @@
+"use client";
+
+import { Router, type RouteDefinition } from "@funstack/router";
+import "./styles.css";
+
+export function ClientApp({ routes }: { routes: RouteDefinition[] }) {
+ // No ssr prop — during SSR only pathless routes match, rendering the app shell.
+ // Path-based content fills in on client hydration.
+ return ;
+}
diff --git a/packages/example-pathless-ssr/src/Root.tsx b/packages/example-pathless-ssr/src/Root.tsx
new file mode 100644
index 0000000..77fb994
--- /dev/null
+++ b/packages/example-pathless-ssr/src/Root.tsx
@@ -0,0 +1,18 @@
+import type { ReactNode } from "react";
+
+export default function Root({ children }: { children: ReactNode }) {
+ return (
+
+
+
+
+ Recipe Book - FUNSTACK Router Pathless SSR Example
+
+
+ {children}
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/components/Header.tsx b/packages/example-pathless-ssr/src/components/Header.tsx
new file mode 100644
index 0000000..10ee54d
--- /dev/null
+++ b/packages/example-pathless-ssr/src/components/Header.tsx
@@ -0,0 +1,28 @@
+import { NavLink } from "./NavLink.js";
+
+const navItems = [
+ { path: "/", label: "Home", exact: true },
+ { path: "/recipes", label: "Recipes" },
+ { path: "/favorites", label: "Favorites" },
+];
+
+export function Header() {
+ return (
+
+ Recipe Book
+
+ {navItems.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/components/Layout.tsx b/packages/example-pathless-ssr/src/components/Layout.tsx
new file mode 100644
index 0000000..739a2f9
--- /dev/null
+++ b/packages/example-pathless-ssr/src/components/Layout.tsx
@@ -0,0 +1,19 @@
+import { Outlet } from "@funstack/router";
+import { Header } from "./Header.js";
+
+export function Layout() {
+ return (
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/components/NavLink.tsx b/packages/example-pathless-ssr/src/components/NavLink.tsx
new file mode 100644
index 0000000..91b7f25
--- /dev/null
+++ b/packages/example-pathless-ssr/src/components/NavLink.tsx
@@ -0,0 +1,57 @@
+"use client";
+
+import { useLocation } from "@funstack/router";
+import { useState, useEffect, type ReactNode } from "react";
+
+type NavLinkProps = {
+ href: string;
+ className: string;
+ activeClassName: string;
+ exact?: boolean;
+ children: ReactNode;
+};
+
+/**
+ * NavLink with active styling that works with pathless SSR.
+ *
+ * During SSR (without the `ssr` prop), `useLocation` is not available because
+ * the URL is null. We defer active-class logic to a child component that only
+ * mounts after hydration, so `useLocation` is never called during SSR.
+ */
+export function NavLink(props: NavLinkProps) {
+ const [isClient, setIsClient] = useState(false);
+ useEffect(() => setIsClient(true), []);
+
+ if (isClient) {
+ return ;
+ }
+
+ // During SSR or initial hydration render: plain link without active styling
+ return (
+
+ {props.children}
+
+ );
+}
+
+function ActiveNavLink({
+ href,
+ className,
+ activeClassName,
+ exact,
+ children,
+}: NavLinkProps) {
+ const location = useLocation();
+ const isActive = exact
+ ? location.pathname === href
+ : location.pathname === href || location.pathname.startsWith(href);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/data/recipes.ts b/packages/example-pathless-ssr/src/data/recipes.ts
new file mode 100644
index 0000000..88599a4
--- /dev/null
+++ b/packages/example-pathless-ssr/src/data/recipes.ts
@@ -0,0 +1,125 @@
+import type { Recipe } from "../features/recipes/types.js";
+
+const recipes: Recipe[] = [
+ {
+ id: "1",
+ title: "Classic Margherita Pizza",
+ description:
+ "A simple Italian pizza with fresh tomatoes, mozzarella, and basil.",
+ ingredients: [
+ "Pizza dough",
+ "San Marzano tomatoes",
+ "Fresh mozzarella",
+ "Fresh basil",
+ "Olive oil",
+ "Salt",
+ ],
+ instructions:
+ "Preheat oven to 475\u00b0F. Roll out dough, spread crushed tomatoes, add torn mozzarella. Bake 10\u201312 minutes. Top with fresh basil and a drizzle of olive oil.",
+ favorite: false,
+ createdAt: "2024-01-15",
+ },
+ {
+ id: "2",
+ title: "Chicken Stir-Fry",
+ description:
+ "A quick weeknight stir-fry with vegetables and a savory sauce.",
+ ingredients: [
+ "Chicken breast",
+ "Bell peppers",
+ "Broccoli",
+ "Soy sauce",
+ "Sesame oil",
+ "Garlic",
+ "Ginger",
+ "Rice",
+ ],
+ instructions:
+ "Slice chicken and vegetables. Heat sesame oil in a wok. Cook chicken until golden, then add vegetables. Stir in soy sauce, garlic, and ginger. Serve over steamed rice.",
+ favorite: true,
+ createdAt: "2024-01-16",
+ },
+ {
+ id: "3",
+ title: "Banana Pancakes",
+ description:
+ "Fluffy pancakes made with ripe bananas for natural sweetness.",
+ ingredients: [
+ "Ripe bananas",
+ "Eggs",
+ "Flour",
+ "Baking powder",
+ "Milk",
+ "Butter",
+ "Maple syrup",
+ ],
+ instructions:
+ "Mash bananas, mix with eggs and milk. Fold in flour and baking powder. Cook on a buttered griddle until bubbles form, then flip. Serve with maple syrup.",
+ favorite: true,
+ createdAt: "2024-01-17",
+ },
+ {
+ id: "4",
+ title: "Caesar Salad",
+ description:
+ "Crisp romaine lettuce with homemade Caesar dressing and croutons.",
+ ingredients: [
+ "Romaine lettuce",
+ "Parmesan cheese",
+ "Croutons",
+ "Egg yolk",
+ "Lemon juice",
+ "Garlic",
+ "Anchovies",
+ "Olive oil",
+ ],
+ instructions:
+ "Whisk egg yolk, lemon juice, minced garlic, and anchovies. Slowly drizzle in olive oil. Toss chopped romaine with dressing, top with shaved Parmesan and croutons.",
+ favorite: false,
+ createdAt: "2024-01-18",
+ },
+];
+
+let nextId = 5;
+
+export function getRecipes(): Recipe[] {
+ return [...recipes];
+}
+
+export function getRecipe(id: string): Recipe | undefined {
+ return recipes.find((r) => r.id === id);
+}
+
+export function createRecipe(
+ title: string,
+ description: string,
+ ingredients: string,
+ instructions: string,
+): Recipe {
+ const recipe: Recipe = {
+ id: String(nextId++),
+ title,
+ description,
+ ingredients: ingredients
+ .split("\n")
+ .map((s) => s.trim())
+ .filter(Boolean),
+ instructions,
+ favorite: false,
+ createdAt: new Date().toISOString().split("T")[0]!,
+ };
+ recipes.push(recipe);
+ return recipe;
+}
+
+export function getFavorites(): Recipe[] {
+ return recipes.filter((r) => r.favorite);
+}
+
+export function toggleFavorite(id: string): Recipe | undefined {
+ const recipe = recipes.find((r) => r.id === id);
+ if (recipe) {
+ recipe.favorite = !recipe.favorite;
+ }
+ return recipe;
+}
diff --git a/packages/example-pathless-ssr/src/entries.tsx b/packages/example-pathless-ssr/src/entries.tsx
new file mode 100644
index 0000000..27e4e63
--- /dev/null
+++ b/packages/example-pathless-ssr/src/entries.tsx
@@ -0,0 +1,52 @@
+import type { EntryDefinition } from "@funstack/static/entries";
+import type { RouteDefinition } from "@funstack/router";
+import { routes } from "./App.js";
+import App from "./App.js";
+
+function collectPaths(routeDefs: RouteDefinition[], prefix: string): string[] {
+ const paths: string[] = [];
+ for (const r of routeDefs) {
+ const routePath = r.path;
+ if (routePath === undefined) {
+ // Pathless route: recurse with same prefix
+ if (r.children) {
+ paths.push(...collectPaths(r.children, prefix));
+ }
+ } else if (routePath.includes(":")) {
+ // Parameterized route: skip (cannot be statically generated)
+ } else if (r.children) {
+ // Has path and children: recurse with new prefix
+ paths.push(...collectPaths(r.children, prefix + routePath));
+ } else {
+ // Leaf route: collect the full path
+ const fullPath = routePath === "/" ? prefix || "/" : prefix + routePath;
+ paths.push(fullPath);
+ }
+ }
+ return paths;
+}
+
+function toEntry(path: string): { outputPath: string } {
+ if (path === "/*") {
+ return { outputPath: "404.html" };
+ }
+ if (path === "/") {
+ return { outputPath: "index.html" };
+ }
+ // Remove leading slash for outputPath
+ const stripped = path.slice(1);
+ return { outputPath: `${stripped}.html` };
+}
+
+export default function getEntries(): EntryDefinition[] {
+ const paths = collectPaths(routes, "");
+ return paths.map((path) => {
+ const { outputPath } = toEntry(path);
+ return {
+ path: outputPath,
+ root: () => import("./Root.js"),
+ // No ssrPath passed — the Router renders only pathless routes (app shell)
+ app: ,
+ };
+ });
+}
diff --git a/packages/example-pathless-ssr/src/features/favorites/FavoritesList.tsx b/packages/example-pathless-ssr/src/features/favorites/FavoritesList.tsx
new file mode 100644
index 0000000..0397ad4
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/favorites/FavoritesList.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import type { RouteComponentPropsOf } from "@funstack/router";
+import { useRouteData } from "@funstack/router";
+import { favoritesRoute } from "./route.js";
+import type { FavoritesState } from "./route.js";
+
+type Props = RouteComponentPropsOf;
+
+const defaultState: FavoritesState = {
+ sortBy: "name",
+};
+
+export function FavoritesList({ state, setStateSync }: Props) {
+ const recipes = useRouteData(favoritesRoute);
+ const { sortBy } = state ?? defaultState;
+
+ const sorted = [...recipes].sort((a, b) => {
+ if (sortBy === "date") {
+ return b.createdAt.localeCompare(a.createdAt);
+ }
+ return a.title.localeCompare(b.title);
+ });
+
+ return (
+
+
Favorites
+
+ Sort by:
+
+ setStateSync((prev) => ({
+ ...defaultState,
+ ...prev,
+ sortBy: e.target.value as "name" | "date",
+ }))
+ }
+ >
+ Name
+ Date Added
+
+
+
+ {sorted.length === 0 ? (
+
+ No favorites yet. Browse recipes and mark some
+ as favorites!
+
+ ) : (
+
+ )}
+
+
+
Route State
+
{JSON.stringify(state ?? defaultState, null, 2)}
+
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/features/favorites/loaders.ts b/packages/example-pathless-ssr/src/features/favorites/loaders.ts
new file mode 100644
index 0000000..f0a2e8b
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/favorites/loaders.ts
@@ -0,0 +1,8 @@
+"use client";
+
+import { getFavorites } from "../../data/recipes.js";
+import type { Recipe } from "../recipes/types.js";
+
+export function loadFavorites(): Recipe[] {
+ return getFavorites();
+}
diff --git a/packages/example-pathless-ssr/src/features/favorites/route.ts b/packages/example-pathless-ssr/src/features/favorites/route.ts
new file mode 100644
index 0000000..bdaef09
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/favorites/route.ts
@@ -0,0 +1,12 @@
+import { routeState } from "@funstack/router/server";
+import { loadFavorites } from "./loaders.js";
+
+export type FavoritesState = {
+ sortBy: "name" | "date";
+};
+
+export const favoritesRoute = routeState()({
+ id: "favorites",
+ path: "/favorites",
+ loader: loadFavorites,
+});
diff --git a/packages/example-pathless-ssr/src/features/home/HomePage.tsx b/packages/example-pathless-ssr/src/features/home/HomePage.tsx
new file mode 100644
index 0000000..b6d2c13
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/home/HomePage.tsx
@@ -0,0 +1,57 @@
+export function HomePage() {
+ return (
+
+
Welcome to Recipe Book
+
+ This app demonstrates pathless SSR with FUNSTACK
+ Router. The <Router> has no ssr prop, so
+ during SSR only pathless routes (the layout shell) are rendered.
+ Path-based content fills in on client hydration.
+
+
+
+
+
+ How Pathless SSR Works
+
+
+ The root Layout is a pathless route {" "}
+ (no path property) — it always matches
+
+
+ During SSR, the Router renders only pathless routes as an{" "}
+ app shell (header, footer, navigation)
+
+
+ Path-based routes (like this page) render on the{" "}
+ client after hydration
+
+
+ This contrasts with pathful SSR where{" "}
+ ssr={{ path }} is passed to render
+ full page content during SSR
+
+
+
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/features/home/route.ts b/packages/example-pathless-ssr/src/features/home/route.ts
new file mode 100644
index 0000000..569ec12
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/home/route.ts
@@ -0,0 +1,6 @@
+import { route } from "@funstack/router/server";
+
+export const homeRoute = route({
+ id: "home",
+ path: "/",
+});
diff --git a/packages/example-pathless-ssr/src/features/recipes/NewRecipeForm.tsx b/packages/example-pathless-ssr/src/features/recipes/NewRecipeForm.tsx
new file mode 100644
index 0000000..655768d
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/NewRecipeForm.tsx
@@ -0,0 +1,60 @@
+import { NewRecipeRedirect } from "./NewRecipeRedirect.js";
+
+export function NewRecipeForm() {
+ return (
+
+
Create New Recipe
+
+
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/features/recipes/NewRecipeRedirect.tsx b/packages/example-pathless-ssr/src/features/recipes/NewRecipeRedirect.tsx
new file mode 100644
index 0000000..5c10493
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/NewRecipeRedirect.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import { useEffect } from "react";
+import { useRouteData } from "@funstack/router";
+import { newRecipeRoute } from "./route.js";
+
+export function NewRecipeRedirect() {
+ const data = useRouteData(newRecipeRoute);
+
+ useEffect(() => {
+ if (data.createdRecipe) {
+ navigation.navigate("/recipes");
+ }
+ }, [data.createdRecipe]);
+
+ return null;
+}
diff --git a/packages/example-pathless-ssr/src/features/recipes/RecipeActions.tsx b/packages/example-pathless-ssr/src/features/recipes/RecipeActions.tsx
new file mode 100644
index 0000000..3bd700d
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/RecipeActions.tsx
@@ -0,0 +1,59 @@
+"use client";
+
+import { useRouteParams, useRouteData } from "@funstack/router";
+import { recipeDetailRoute } from "./route.js";
+import { toggleFavorite } from "../../data/recipes.js";
+
+export function RecipeActions() {
+ const { recipeId } = useRouteParams(recipeDetailRoute);
+ const recipe = useRouteData(recipeDetailRoute);
+
+ if (!recipe) {
+ return (
+
+
Recipe not found
+
No recipe with ID “{recipeId}” exists.
+
Back to recipes
+
+ );
+ }
+
+ const handleToggleFavorite = () => {
+ toggleFavorite(recipe.id);
+ navigation.navigate(`/recipes/${recipe.id}`);
+ };
+
+ return (
+
+
+
{recipe.title}
+
+ {recipe.favorite ? "Unfavorite" : "Favorite"}
+
+
+
{recipe.description}
+
Added: {recipe.createdAt}
+
+
+ Ingredients
+
+ {recipe.ingredients.map((ingredient, i) => (
+ {ingredient}
+ ))}
+
+
+
+
+ Instructions
+ {recipe.instructions}
+
+
+
+ ← Back to recipes
+
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/features/recipes/RecipeDetailPage.tsx b/packages/example-pathless-ssr/src/features/recipes/RecipeDetailPage.tsx
new file mode 100644
index 0000000..9cadfb5
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/RecipeDetailPage.tsx
@@ -0,0 +1,5 @@
+import { RecipeActions } from "./RecipeActions.js";
+
+export function RecipeDetailPage() {
+ return ;
+}
diff --git a/packages/example-pathless-ssr/src/features/recipes/RecipeList.tsx b/packages/example-pathless-ssr/src/features/recipes/RecipeList.tsx
new file mode 100644
index 0000000..9a54186
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/RecipeList.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import { useRouteData } from "@funstack/router";
+import { recipeListRoute } from "./route.js";
+
+export function RecipeList() {
+ const recipes = useRouteData(recipeListRoute);
+
+ if (recipes.length === 0) {
+ return No recipes yet. Create one!
;
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/features/recipes/RecipeListPage.tsx b/packages/example-pathless-ssr/src/features/recipes/RecipeListPage.tsx
new file mode 100644
index 0000000..52a78ba
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/RecipeListPage.tsx
@@ -0,0 +1,15 @@
+import { RecipeList } from "./RecipeList.js";
+
+export function RecipeListPage() {
+ return (
+
+ );
+}
diff --git a/packages/example-pathless-ssr/src/features/recipes/loaders.ts b/packages/example-pathless-ssr/src/features/recipes/loaders.ts
new file mode 100644
index 0000000..23d92aa
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/loaders.ts
@@ -0,0 +1,34 @@
+"use client";
+
+import type { ActionArgs, LoaderArgs } from "@funstack/router";
+import { getRecipes, getRecipe, createRecipe } from "../../data/recipes.js";
+import type { Recipe } from "./types.js";
+
+export function loadRecipeList(): Recipe[] {
+ return getRecipes();
+}
+
+export function loadRecipeDetail({
+ params,
+}: LoaderArgs<{ recipeId: string }>): Recipe | undefined {
+ return getRecipe(params.recipeId);
+}
+
+export async function createRecipeAction({
+ request,
+}: ActionArgs>): Promise {
+ const formData = await request.formData();
+ const title = formData.get("title") as string;
+ const description = formData.get("description") as string;
+ const ingredients = formData.get("ingredients") as string;
+ const instructions = formData.get("instructions") as string;
+ return createRecipe(title, description, ingredients, instructions);
+}
+
+export function loadNewRecipeResult({
+ actionResult,
+}: LoaderArgs, Recipe>): {
+ createdRecipe: Recipe | null;
+} {
+ return { createdRecipe: actionResult ?? null };
+}
diff --git a/packages/example-pathless-ssr/src/features/recipes/route.ts b/packages/example-pathless-ssr/src/features/recipes/route.ts
new file mode 100644
index 0000000..e0bb6e7
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/route.ts
@@ -0,0 +1,26 @@
+import { route } from "@funstack/router/server";
+import {
+ loadRecipeList,
+ loadRecipeDetail,
+ createRecipeAction,
+ loadNewRecipeResult,
+} from "./loaders.js";
+
+export const recipeListRoute = route({
+ id: "recipeList",
+ path: "/recipes",
+ loader: loadRecipeList,
+});
+
+export const newRecipeRoute = route({
+ id: "newRecipe",
+ path: "/recipes/new",
+ action: createRecipeAction,
+ loader: loadNewRecipeResult,
+});
+
+export const recipeDetailRoute = route({
+ id: "recipeDetail",
+ path: "/recipes/:recipeId",
+ loader: loadRecipeDetail,
+});
diff --git a/packages/example-pathless-ssr/src/features/recipes/types.ts b/packages/example-pathless-ssr/src/features/recipes/types.ts
new file mode 100644
index 0000000..37aaf1e
--- /dev/null
+++ b/packages/example-pathless-ssr/src/features/recipes/types.ts
@@ -0,0 +1,9 @@
+export interface Recipe {
+ id: string;
+ title: string;
+ description: string;
+ ingredients: string[];
+ instructions: string;
+ favorite: boolean;
+ createdAt: string;
+}
diff --git a/packages/example-pathless-ssr/src/styles.css b/packages/example-pathless-ssr/src/styles.css
new file mode 100644
index 0000000..10a49cc
--- /dev/null
+++ b/packages/example-pathless-ssr/src/styles.css
@@ -0,0 +1,389 @@
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family:
+ system-ui,
+ -apple-system,
+ sans-serif;
+ line-height: 1.6;
+ color: #1a1a2e;
+ background: #f5f5f7;
+}
+
+a {
+ color: #4361ee;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+/* Layout */
+
+.layout {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+.main {
+ flex: 1;
+ max-width: 800px;
+ width: 100%;
+ margin: 0 auto;
+ padding: 2rem 1rem;
+}
+
+.footer {
+ text-align: center;
+ padding: 1.5rem;
+ color: #666;
+ font-size: 0.875rem;
+ border-top: 1px solid #e0e0e0;
+}
+
+.footer code {
+ background: #e8eaf6;
+ padding: 0.15em 0.4em;
+ border-radius: 4px;
+ font-size: 0.875em;
+}
+
+/* Header */
+
+.header {
+ background: #2d6a4f;
+ color: #fff;
+ padding: 1rem 2rem;
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+}
+
+.header-title {
+ font-size: 1.25rem;
+ font-weight: 700;
+}
+
+.header-nav {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.nav-link {
+ color: rgba(255, 255, 255, 0.7);
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-size: 0.875rem;
+ transition: background 0.2s;
+}
+
+.nav-link:hover {
+ background: rgba(255, 255, 255, 0.1);
+ color: #fff;
+ text-decoration: none;
+}
+
+.nav-link.active {
+ background: rgba(255, 255, 255, 0.15);
+ color: #fff;
+}
+
+/* Home */
+
+.home-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+ gap: 1rem;
+ margin: 1.5rem 0;
+}
+
+.home-card {
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ padding: 1.25rem;
+ transition:
+ box-shadow 0.2s,
+ border-color 0.2s;
+}
+
+.home-card:hover {
+ border-color: #2d6a4f;
+ box-shadow: 0 2px 8px rgba(45, 106, 79, 0.15);
+ text-decoration: none;
+}
+
+.home-card h3 {
+ margin-bottom: 0.5rem;
+ color: #1a1a2e;
+}
+
+.home-card p {
+ color: #666;
+ font-size: 0.875rem;
+}
+
+.home-info {
+ margin-top: 2rem;
+}
+
+.home-info h3 {
+ margin-bottom: 0.75rem;
+}
+
+.home-info ul {
+ padding-left: 1.5rem;
+}
+
+.home-info li {
+ margin-bottom: 0.5rem;
+}
+
+.home-info code {
+ background: #e8eaf6;
+ padding: 0.15em 0.4em;
+ border-radius: 4px;
+ font-size: 0.875em;
+}
+
+/* Page Header */
+
+.page-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 1.5rem;
+}
+
+/* Recipe List */
+
+.recipe-list {
+ list-style: none;
+}
+
+.recipe-item {
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ margin-bottom: 0.5rem;
+ background: #fff;
+ transition: border-color 0.2s;
+}
+
+.recipe-item:hover {
+ border-color: #2d6a4f;
+}
+
+.recipe-link {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 1rem 1.25rem;
+ color: inherit;
+}
+
+.recipe-link:hover {
+ text-decoration: none;
+}
+
+.recipe-info {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.recipe-title {
+ font-weight: 500;
+}
+
+.recipe-description {
+ color: #666;
+ font-size: 0.875rem;
+}
+
+.recipe-favorite {
+ font-size: 1.25rem;
+ color: #e65100;
+}
+
+.empty-state {
+ text-align: center;
+ color: #666;
+ padding: 3rem;
+}
+
+/* Recipe Detail */
+
+.recipe-detail-header {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.recipe-description-text {
+ color: #444;
+ margin-bottom: 0.5rem;
+}
+
+.recipe-meta {
+ color: #888;
+ font-size: 0.875rem;
+ margin-bottom: 1.5rem;
+}
+
+.recipe-section {
+ margin-bottom: 1.5rem;
+}
+
+.recipe-section h3 {
+ margin-bottom: 0.5rem;
+ color: #2d6a4f;
+}
+
+.ingredient-list {
+ padding-left: 1.5rem;
+}
+
+.ingredient-list li {
+ margin-bottom: 0.25rem;
+}
+
+.recipe-instructions {
+ color: #444;
+ line-height: 1.8;
+}
+
+.back-link {
+ display: inline-block;
+ margin-top: 0.5rem;
+}
+
+.not-found {
+ text-align: center;
+ padding: 3rem;
+}
+
+/* New Recipe Form */
+
+.recipe-form {
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 8px;
+ padding: 1.5rem;
+}
+
+.form-field {
+ margin-bottom: 1.25rem;
+}
+
+.form-field label {
+ display: block;
+ font-weight: 500;
+ margin-bottom: 0.375rem;
+}
+
+.form-field input,
+.form-field textarea {
+ width: 100%;
+ padding: 0.5rem 0.75rem;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ font-family: inherit;
+ font-size: 1rem;
+}
+
+.form-field input:focus,
+.form-field textarea:focus {
+ outline: none;
+ border-color: #2d6a4f;
+ box-shadow: 0 0 0 3px rgba(45, 106, 79, 0.15);
+}
+
+.form-actions {
+ display: flex;
+ gap: 0.75rem;
+}
+
+/* Buttons */
+
+.btn {
+ display: inline-block;
+ padding: 0.5rem 1rem;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ background: #fff;
+ color: #333;
+ font-size: 0.875rem;
+ cursor: pointer;
+ transition:
+ background 0.2s,
+ border-color 0.2s;
+}
+
+.btn:hover {
+ background: #f0f0f0;
+ text-decoration: none;
+}
+
+.btn-primary {
+ background: #2d6a4f;
+ color: #fff;
+ border-color: #2d6a4f;
+}
+
+.btn-primary:hover {
+ background: #245a42;
+}
+
+.btn-active {
+ background: #e8f5e9;
+ border-color: #2d6a4f;
+ color: #2d6a4f;
+}
+
+.btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Favorites */
+
+.favorites-controls {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.favorites-controls select {
+ padding: 0.375rem 0.75rem;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ font-size: 0.875rem;
+}
+
+.favorites-state-preview {
+ margin-top: 1.5rem;
+ background: #f5f5f7;
+ border-radius: 8px;
+ padding: 1rem;
+}
+
+.favorites-state-preview h4 {
+ margin-bottom: 0.5rem;
+ font-size: 0.875rem;
+ color: #666;
+}
+
+.favorites-state-preview pre {
+ font-size: 0.875rem;
+ color: #333;
+}
diff --git a/packages/example-pathless-ssr/src/vite-env.d.ts b/packages/example-pathless-ssr/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/packages/example-pathless-ssr/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/example-pathless-ssr/tsconfig.json b/packages/example-pathless-ssr/tsconfig.json
new file mode 100644
index 0000000..577b306
--- /dev/null
+++ b/packages/example-pathless-ssr/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/packages/example-pathless-ssr/vite.config.ts b/packages/example-pathless-ssr/vite.config.ts
new file mode 100644
index 0000000..877736a
--- /dev/null
+++ b/packages/example-pathless-ssr/vite.config.ts
@@ -0,0 +1,14 @@
+import react from "@vitejs/plugin-react";
+import funstackStatic from "@funstack/static";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [
+ funstackStatic({
+ entries: "./src/entries.tsx",
+ ssr: true,
+ }),
+ react(),
+ ],
+ base: "/",
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index be8e4c2..b7a310d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -83,6 +83,37 @@ importers:
specifier: ^8.0.3
version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)
+ packages/example-pathless-ssr:
+ dependencies:
+ '@funstack/router':
+ specifier: workspace:*
+ version: link:../router
+ '@funstack/static':
+ specifier: ^1.1.2
+ version: 1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4))
+ react:
+ specifier: ^19.2.4
+ version: 19.2.4
+ react-dom:
+ specifier: ^19.2.4
+ 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: ^6.0.1
+ version: 6.0.1(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4))
+ typescript:
+ specifier: ^6.0.2
+ version: 6.0.2
+ vite:
+ specifier: ^8.0.3
+ version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.4)
+
packages/example-rsc:
dependencies:
'@funstack/router':