{"id":26354415,"url":"https://github.com/wakujs/waku","last_synced_at":"2026-01-16T09:02:13.318Z","repository":{"id":102173023,"uuid":"609137611","full_name":"wakujs/waku","owner":"wakujs","description":"⛩️ The minimal React framework","archived":false,"fork":false,"pushed_at":"2026-01-11T10:56:29.000Z","size":7912,"stargazers_count":6053,"open_issues_count":25,"forks_count":188,"subscribers_count":13,"default_branch":"main","last_synced_at":"2026-01-11T11:04:59.611Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://waku.gg","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wakujs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["dai-shi"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2023-03-03T13:05:39.000Z","updated_at":"2026-01-11T08:18:06.000Z","dependencies_parsed_at":"2024-01-16T09:38:46.613Z","dependency_job_id":"5a2294f7-b1a9-4186-82dc-18972368a5b4","html_url":"https://github.com/wakujs/waku","commit_stats":null,"previous_names":["dai-shi/wakuwork","wakujs/waku"],"tags_count":138,"template":false,"template_full_name":null,"purl":"pkg:github/wakujs/waku","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wakujs%2Fwaku","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wakujs%2Fwaku/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wakujs%2Fwaku/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wakujs%2Fwaku/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wakujs","download_url":"https://codeload.github.com/wakujs/waku/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wakujs%2Fwaku/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-16T12:03:11.446Z","updated_at":"2026-01-16T09:02:13.224Z","avatar_url":"https://github.com/wakujs.png","language":"TypeScript","readme":"# Waku\n\n⛩️ The minimal React framework\n\nvisit [waku.gg](https://waku.gg) or `npm create waku@latest`\n\n[![Build Status](https://img.shields.io/github/actions/workflow/status/wakujs/waku/test.yml?branch=main\u0026style=flat\u0026colorA=000000\u0026colorB=000000)](https://github.com/wakujs/waku/actions?query=workflow%3ATest)\n[![Version](https://img.shields.io/npm/v/waku?style=flat\u0026colorA=000000\u0026colorB=000000)](https://www.npmjs.com/package/waku)\n[![Downloads](https://img.shields.io/npm/dt/waku.svg?style=flat\u0026colorA=000000\u0026colorB=000000)](https://www.npmjs.com/package/waku)\n[![Discord Shield](https://img.shields.io/discord/627656437971288081?style=flat\u0026colorA=000000\u0026colorB=000000\u0026label=discord\u0026logo=discord\u0026logoColor=ffffff)](https://discord.gg/MrQdmzd)\n\n\u003cbr\u003e\n\n## Introduction\n\n**Waku** _(wah-ku)_ or **わく** is the minimal React framework. It’s lightweight and designed for a fun developer experience, yet supports all the latest React 19 features like server components and actions. Built for marketing sites, headless commerce, and web apps. For large enterprise applications, you may prefer a heavier framework.\n\n\u003e Please try Waku on non-production projects and report any issues you find. Contributors are welcome.\n\n## Getting started\n\nStart a new Waku project with the `create` command for your preferred package manager. It will scaffold a new project with our default [Waku starter](https://github.com/wakujs/waku/tree/main/examples/01_template).\n\n```sh\nnpm create waku@latest\n```\n\n#### Commands\n\n- `waku dev` to start the local development server\n- `waku build` to generate a production build\n- `waku start` to serve the production build locally\n\n**Node.js version requirement:** `^24.0.0` or `^22.12.0` or `^20.19.0`\n\n## Rendering\n\nWhile there’s a bit of a learning curve to modern React rendering, it introduces powerful new patterns of full-stack composability that are only possible with the advent of [server components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md).\n\nSo please don’t be intimidated by the `'use client'` directive! Once you get the hang of it, you’ll appreciate how awesome it is to flexibly move server-client boundaries with a single line of code as your full-stack React codebase evolves over time. It’s way simpler than maintaining separate codebases for your backend and frontend.\n\nAnd please don’t fret about client components! Even if you only lightly optimize towards server components, your client bundle size will be smaller than traditional React frameworks, which are always 100% client components.\n\n\u003e Future versions of Waku may provide additional opt-in APIs to abstract some of the complexity away for an improved developer experience.\n\n#### Server components\n\nServer components can be made async and can securely perform server-side logic and data fetching. Feel free to access the local file-system and import heavy dependencies since they aren’t included in the client bundle. They have no state, interactivity, or access to browser APIs since they run _exclusively_ on the server.\n\n```tsx\n// server component\nimport db from 'some-db';\n\nimport { Gallery } from '../components/gallery';\n\nexport const Store = async () =\u003e {\n  const products = await db.query('SELECT * FROM products');\n\n  return \u003cGallery products={products} /\u003e;\n};\n```\n\n#### Client components\n\nA `'use client'` directive placed at the top of a file will create a server-client boundary when imported into a server component. All components imported below the boundary will be hydrated to run in the browser as well. They can use all traditional React features such as state, effects, and event handlers.\n\n```tsx\n// client component\n'use client';\n\nimport { useState } from 'react';\n\nexport const Counter = () =\u003e {\n  const [count, setCount] = useState(0);\n\n  return (\n    \u003c\u003e\n      \u003cdiv\u003eCount: {count}\u003c/div\u003e\n      \u003cbutton onClick={() =\u003e setCount((c) =\u003e c + 1)}\u003eIncrement\u003c/button\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n#### Shared components\n\nSimple React components that [meet all of the rules](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#sharing-code-between-server-and-client) of both server and client components can be imported into either server or client components without affecting the server-client boundary.\n\n```tsx\n// shared component\nexport const Headline = ({ children }) =\u003e {\n  return \u003ch3\u003e{children}\u003c/h3\u003e;\n};\n```\n\n#### Weaving patterns\n\nServer components can import client components and doing so will create a server-client boundary. Client components cannot import server components, but they can accept server components as props such as `children`. For example, you may want to add global context providers this way.\n\n```tsx\n// ./src/pages/_layout.tsx\nimport { Providers } from '../components/providers';\n\nexport default async function RootLayout({ children }) {\n  return (\n    \u003cProviders\u003e\n      \u003cmain\u003e{children}\u003c/main\u003e\n    \u003c/Providers\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n```tsx\n// ./src/components/providers.tsx\n'use client';\n\nimport { Provider } from 'jotai';\n\nexport const Providers = ({ children }) =\u003e {\n  return \u003cProvider\u003e{children}\u003c/Provider\u003e;\n};\n```\n\n#### Server-side rendering\n\nWaku provides static prerendering (SSG) and server-side rendering (SSR) options for both layouts and pages including all of their server _and_ client components. Note that SSR is a distinct concept from RSC.\n\n#### tl;dr:\n\nEach layout and page in Waku is composed of a React component hierarchy.\n\nIt begins with a server component at the top of the tree. Then at points down the hierarchy, you’ll eventually import a component that needs client component APIs. Mark this file with a `'use client'` directive at the top. When imported into a server component, it will create a server-client boundary. Below this point, all imported components are hydrated and will run in the browser as well.\n\nServer components can be rendered below this boundary, but only via composition (e.g., `children` props). Together they form [a new “React server” layer](https://github.com/reactwg/server-components/discussions/4) that runs _before_ the traditional “React client” layer with which you’re already familiar.\n\nClient components are still server-side rendered as SSR is separate from RSC. Please see the [linked diagrams](https://github.com/reactwg/server-components/discussions/4) for a helpful visual.\n\n#### Further reading\n\nTo learn more about the modern React architecture, we recommend [Making Sense of React Server Components](https://www.joshwcomeau.com/react/server-components/) and [The Two Reacts](https://overreacted.io/the-two-reacts/).\n\n## Routing\n\nWaku provides a minimal file-based “pages router” experience built for the server components era.\n\nIts underlying [low-level API](https://github.com/wakujs/waku/blob/main/docs/create-pages.mdx) is also available for those that prefer programmatic routing. This documentation covers file-based routing since many React developers prefer it, but please feel free to try both and see which you like more!\n\n### Overview\n\nThe directory for file-based routing in Waku projects is `./src/pages`.\n\nLayouts and pages can be created by making a new file with two exports: a default function for the React component and a named `getConfig` function that returns a configuration object to specify the render method and other options.\n\nWaku currently supports two rendering options:\n\n- `'static'` for static prerendering (SSG)\n\n- `'dynamic'` for server-side rendering (SSR)\n\nLayouts, pages, and slices are all `static` by default, while api handlers default to `dynamic`.\n\nFor example, you can statically prerender a global header and footer in the root layout at build time, but dynamically render the rest of a home page at request time for personalized user experiences.\n\n```tsx\n// ./src/pages/_layout.tsx\nimport '../styles.css';\n\nimport { Providers } from '../components/providers';\nimport { Header } from '../components/header';\nimport { Footer } from '../components/footer';\n\n// Create root layout\nexport default async function RootLayout({ children }) {\n  return (\n    \u003cProviders\u003e\n      \u003cHeader /\u003e\n      \u003cmain\u003e{children}\u003c/main\u003e\n      \u003cFooter /\u003e\n    \u003c/Providers\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n```tsx\n// ./src/pages/index.tsx\n\n// Create home page\nexport default async function HomePage() {\n  const data = await getData();\n\n  return (\n    \u003c\u003e\n      \u003ch1\u003e{data.title}\u003c/h1\u003e\n      \u003cdiv\u003e{data.content}\u003c/div\u003e\n    \u003c/\u003e\n  );\n}\n\nconst getData = async () =\u003e {\n  /* ... */\n};\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'dynamic',\n  } as const;\n};\n```\n\n### Pages\n\nPages render a single route, segment route, or catch-all route based on the file system path (conventions below). All page components automatically receive two props related to the rendered route: `path` (string) and `query` (string).\n\n#### Single routes\n\nPages can be rendered as a single route (e.g., `about.tsx` or `blog/index.tsx`).\n\n```tsx\n// ./src/pages/about.tsx\n\n// Create about page\nexport default async function AboutPage() {\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n```tsx\n// ./src/pages/blog/index.tsx\n\n// Create blog index page\nexport default async function BlogIndexPage() {\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n#### Segment routes\n\nSegment routes (e.g., `[slug].tsx` or `[slug]/index.tsx`) are marked with brackets.\n\nThe rendered React component automatically receives a prop named by the segment (e.g., `slug`) with the value of the rendered segment (e.g., `'introducing-waku'`).\n\nIf statically prerendering a segment route at build time, a `staticPaths` array must also be provided.\n\n```tsx\n// ./src/pages/blog/[slug].tsx\nimport type { PageProps } from 'waku/router';\n\n// Create blog article pages\nexport default async function BlogArticlePage({\n  slug,\n}: PageProps\u003c'/blog/[slug]'\u003e) {\n  const data = await getData(slug);\n\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nconst getData = async (slug) =\u003e {\n  /* ... */\n};\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n    staticPaths: ['introducing-waku', 'introducing-pages-router'],\n  } as const;\n};\n```\n\n```tsx\n// ./src/pages/shop/[category].tsx\nimport type { PageProps } from 'waku/router';\n\n// Create product category pages\nexport default async function ProductCategoryPage({\n  category,\n}: PageProps\u003c'/shop/[category]'\u003e) {\n  const data = await getData(category);\n\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nconst getData = async (category) =\u003e {\n  /* ... */\n};\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'dynamic',\n  } as const;\n};\n```\n\nStatic paths (or other config values) can also be generated programmatically.\n\n```tsx\n// ./src/pages/blog/[slug].tsx\nimport type { PageProps } from 'waku/router';\n\n// Create blog article pages\nexport default async function BlogArticlePage({\n  slug,\n}: PageProps\u003c'/blog/[slug]'\u003e) {\n  const data = await getData(slug);\n\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nconst getData = async (slug) =\u003e {\n  /* ... */\n};\n\nexport const getConfig = async () =\u003e {\n  const staticPaths = await getStaticPaths();\n\n  return {\n    render: 'static',\n    staticPaths,\n  } as const;\n};\n\nconst getStaticPaths = async () =\u003e {\n  /* ... */\n};\n```\n\n#### Nested segment routes\n\nRoutes can contain multiple segments (e.g., `/shop/[category]/[product]`) by creating folders with brackets as well.\n\n```tsx\n// ./src/pages/shop/[category]/[product].tsx\nimport type { PageProps } from 'waku/router';\n\n// Create product category pages\nexport default async function ProductDetailPage({\n  category,\n  product,\n}: PageProps\u003c'/shop/[category]/[product]'\u003e) {\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'dynamic',\n  } as const;\n};\n```\n\nFor static prerendering of nested segment routes, the `staticPaths` array is instead composed of ordered arrays.\n\n```tsx\n// ./src/pages/shop/[category]/[product].tsx\nimport type { PageProps } from 'waku/router';\n\n// Create product detail pages\nexport default async function ProductDetailPage({\n  category,\n  product,\n}: PageProps\u003c'/shop/[category]/[product]'\u003e) {\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n    staticPaths: [\n      ['same-category', 'some-product'],\n      ['same-category', 'another-product'],\n    ],\n  } as const;\n};\n```\n\n#### Catch-all routes\n\nCatch-all or “wildcard” segment routes (e.g., `/app/[...catchAll]`) are marked with an ellipsis before the name and have indefinite segments.\n\nWildcard routes receive a prop with segment values as an ordered array. For example, the `/app/profile/settings` route would receive a `catchAll` prop with the value `['profile', 'settings']`. These values can then be used to determine what to render in the component.\n\n```tsx\n// ./src/pages/app/[...catchAll].tsx\nimport type { PageProps } from 'waku/router';\n\n// Create dashboard page\nexport default async function DashboardPage({\n  catchAll,\n}: PageProps\u003c'/app/[...catchAll]'\u003e) {\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'dynamic',\n  } as const;\n};\n```\n\n#### Group routes\n\nGroup routes allow you to organize routes into logical groups without affecting the URL structure. They're created by wrapping directory names in parentheses (e.g., `(group)`). This is particularly useful for sharing layouts across multiple routes while keeping the URL clean.\n\nFor example, you might want a home page at `/` that doesn't use a shared layout, but all other routes should share a common layout. This can be achieved by grouping those routes:\n\n```\n├── (main)\n│ ├── _layout.tsx\n│ ├── about.tsx\n│ └── contact.tsx\n└── index.tsx\n```\n\nIn this structure, `/about` and `/contact` will use the layout from `(main)/_layout.tsx`, but `/` (from `index.tsx`) will not.\n\n```tsx\n// ./src/pages/(main)/_layout.tsx\nimport { Header } from '../../components/header';\nimport { Footer } from '../../components/footer';\n\n// Create shared layout for main pages\nexport default async function MainLayout({ children }) {\n  return (\n    \u003c\u003e\n      \u003cHeader /\u003e\n      \u003cmain\u003e{children}\u003c/main\u003e\n      \u003cFooter /\u003e\n    \u003c/\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n```tsx\n// ./src/pages/(main)/about.tsx\nexport default async function AboutPage() {\n  return \u003ch1\u003eAbout Us\u003c/h1\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\nGroup routes can be nested to create complex layout compositions. For instance, you could have a static layout at the group level and a dynamic layout nested within:\n\n```\n(main)\n├── (dynamic)\n│ ├── _layout.tsx # dynamic layout\n│ ├── dashboard.tsx\n│ └── profile.tsx\n└── _layout.tsx # static layout\n```\n\nThis allows for fine-grained control over rendering modes - some work can be done at build time (`static`) while other work happens at runtime (`dynamic`).\n\n```tsx\n// ./src/pages/(main)/_layout.tsx\n// Static layout - runs at build time\nexport default async function MainLayout({ children }) {\n  return \u003cdiv className=\"main-container\"\u003e{children}\u003c/div\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n```tsx\n// ./src/pages/(main)/(dynamic)/_layout.tsx\n// Dynamic layout - runs at request time\nexport default async function DynamicLayout({ children }) {\n  const userData = await fetchUserData(); // Dynamic data fetching\n\n  return (\n    \u003cdiv className=\"dynamic-container\"\u003e\n      \u003cUserContext.Provider value={userData}\u003e{children}\u003c/UserContext.Provider\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'dynamic',\n  } as const;\n};\n```\n\nGroup routes are especially powerful for organizing complex applications where different sections need different layouts, state management, or data requirements while maintaining clean URLs.\n\n#### Ignored routes\n\nThe following directories are ignored by the router:\n\n- `_components`\n- `_hooks`\n\nAll files inside there directories are excluded from routing.\n\nFor instance, in the case below, `pages/about.tsx` is routed to `/about`, but files like `_components/header.tsx` are not routed anywhere.\n\n```\npages/\n├── about.tsx\n├── _components/\n│   ├── header.tsx   // 👈🏼 ignored\n│   ├── footer.tsx   // 👈🏼 ignored\n│   ├── ...          // 👈🏼 ignored\n```\n\n### Router paths type safety\n\nImport `PageProps` from `waku/router` for type-safe access to route parameters (as shown in the examples above). The type provides `path`, `query`, and all segment parameters:\n\n```ts\nPageProps\u003c'/blog/[slug]'\u003e;\n// =\u003e { path: string; slug: string; query: string }\n\nPageProps\u003c'/shop/[category]/[product]'\u003e;\n// =\u003e { path: string; category: string; product: string; query: string }\n```\n\n### Layouts\n\nLayouts are created with a special `_layout.tsx` file name and wrap the entire route and its descendants. They must accept a `children` prop of type `ReactNode`. While not required, you will typically want at least a root layout.\n\n#### Root layout\n\nThe root layout placed at `./pages/_layout.tsx` is especially useful. It can be used for setting global styles, global metadata, global providers, global data, and global components, such as a header and footer.\n\n```tsx\n// ./src/pages/_layout.tsx\nimport '../styles.css';\n\nimport { Providers } from '../components/providers';\nimport { Header } from '../components/header';\nimport { Footer } from '../components/footer';\n\n// Create root layout\nexport default async function RootLayout({ children }) {\n  return (\n    \u003cProviders\u003e\n      \u003clink rel=\"icon\" type=\"image/png\" href=\"/images/favicon.png\" /\u003e\n      \u003cmeta property=\"og:image\" content=\"/images/opengraph.png\" /\u003e\n      \u003cHeader /\u003e\n      \u003cmain\u003e{children}\u003c/main\u003e\n      \u003cFooter /\u003e\n    \u003c/Providers\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n```tsx\n// ./src/components/providers.tsx\n'use client';\n\nimport { createStore, Provider } from 'jotai';\n\nconst store = createStore();\n\nexport const Providers = ({ children }) =\u003e {\n  return \u003cProvider store={store}\u003e{children}\u003c/Provider\u003e;\n};\n```\n\n#### Other layouts\n\nLayouts are also helpful in nested routes. For example, you can add a layout at `./pages/blog/_layout.tsx` to add a sidebar to both the blog index and all blog article pages.\n\n```tsx\n// ./src/pages/blog/_layout.tsx\nimport { Sidebar } from '../../components/sidebar';\n\n// Create blog layout\nexport default async function BlogLayout({ children }) {\n  return (\n    \u003cdiv className=\"flex\"\u003e\n      \u003cdiv\u003e{children}\u003c/div\u003e\n      \u003cSidebar /\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n### Root element\n\nThe attributes of `\u003chtml\u003e`, `\u003chead\u003e`, or `\u003cbody\u003e` elements can be customized with the root element API. Create a special `_root.tsx` file in the `./src/pages` directory that accepts a `children` prop of type `ReactNode`.\n\n```tsx\n// ./src/pages/_root.tsx\n\n// Create root element\nexport default async function RootElement({ children }) {\n  return (\n    \u003chtml lang=\"en\"\u003e\n      \u003chead\u003e\u003c/head\u003e\n      \u003cbody data-version=\"1.0\"\u003e{children}\u003c/body\u003e\n    \u003c/html\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n### Slices\n\nSlices are reusable components that are defined in the `src/pages/_slices` directory. They allow you to compose pages by assembling components like normal React components while specifying alternate rendering patterns.\n\n#### Creating slices\n\nSlices are created by placing files in the `src/pages/_slices` directory. The slice ID corresponds to the filename, and nested slices use the full path as the ID.\n\n```\n src/pages\n ├── _slices\n │   ├── one.tsx\n │   ├── two.tsx\n │   └── nested\n │       └── three.tsx\n └── some-page.tsx\n```\n\nEach slice file exports a default React component and a `getConfig` function that specifies the render method.\n\n```tsx\n// ./src/pages/_slices/one.tsx\n\n// Create slice component\nexport default function SliceOne() {\n  return \u003cp\u003e🍕\u003c/p\u003e;\n}\n\nexport const getConfig = () =\u003e {\n  return {\n    render: 'static', // default is 'static'\n  };\n};\n```\n\n```tsx\n// ./src/pages/_slices/nested/three.tsx\n\n// Create nested slice component\nexport default function SliceThree() {\n  return \u003cp\u003e🍰\u003c/p\u003e;\n}\n\nexport const getConfig = () =\u003e {\n  return {\n    render: 'dynamic',\n  };\n};\n```\n\n#### Using slices\n\nSlices are used in pages and layouts by importing the `Slice` component from Waku and specifying the slice ID. The `slices` array in the page's `getConfig` must include all slice IDs used on that page.\n\n```tsx\n// ./src/pages/some-page.tsx\nimport { Slice } from 'waku';\n\n// Create page with slices\nexport default function SomePage() {\n  return (\n    \u003cdiv\u003e\n      \u003cSlice id=\"one\" /\u003e\n      \u003cSlice id=\"two\" /\u003e\n      \u003cSlice id=\"nested/three\" /\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport const getConfig = () =\u003e {\n  return {\n    render: 'static',\n    slices: ['one', 'two', 'nested/three'],\n  };\n};\n```\n\n#### Lazy slices\n\nLazy slices allow components to be requested independently from the page they are used on, similar to Astro's server islands feature. This is useful for components that will be dynamically rendered on otherwise static pages.\n\nLazy slices are marked with the `lazy` prop and can include a `fallback` component to display while loading.\n\n```tsx\n// ./src/pages/some-page.tsx\nimport { Slice } from 'waku';\n\n// Create page with lazy slice\nexport default function SomePage() {\n  return (\n    \u003cdiv\u003e\n      \u003cSlice id=\"one\" /\u003e\n      \u003cSlice id=\"two\" lazy fallback={\u003cp\u003eTwo is loading...\u003c/p\u003e} /\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport const getConfig = () =\u003e {\n  return {\n    render: 'static',\n    slices: ['one'], // Note: 'two' is lazy, so it is not included\n  };\n};\n```\n\nThis allows you to have a `dynamic` slice component while keeping the rest of the page static.\n\n## Navigation\n\n### Link\n\nThe`\u003cLink /\u003e` component should be used for internal links. It accepts a `to` prop for the destination, which is automatically prefetched ahead of the navigation.\n\n```tsx\n// ./src/pages/index.tsx\nimport { Link } from 'waku';\n\nexport default async function HomePage() {\n  return (\n    \u003c\u003e\n      \u003ch1\u003eHome\u003c/h1\u003e\n      \u003cLink to=\"/about\"\u003eAbout\u003c/Link\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n### useRouter\n\nThe `useRouter` hook can be used to inspect the current route or perform programmatic navigation.\n\n#### router properties\n\nThe `router` object has two properties related to the current route: `path` (string) and `query` (string).\n\n```tsx\n'use client';\n\nimport { useRouter } from 'waku';\n\nexport const Component = () =\u003e {\n  const { path, query } = useRouter();\n\n  return (\n    \u003c\u003e\n      \u003cdiv\u003ecurrent path: {path}\u003c/div\u003e\n      \u003cdiv\u003ecurrent query: {query}\u003c/div\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n#### router methods\n\nThe `router` object also contains several methods for programmatic navigation:\n\n- `router.push(to: string)` - navigate to the provided route\n\n- `router.prefetch(to: string)` - prefetch the provided route\n\n- `router.replace(to: string)` - replace the current history entry\n\n- `router.reload()` - reload the current route\n\n- `router.back()` - navigate to the previous entry in the session history\n\n- `router.forward()` - navigate to the next entry in the session history\n\n```tsx\n'use client';\n\nimport { useRouter } from 'waku';\n\nexport const Component = () =\u003e {\n  const router = useRouter();\n\n  return (\n    \u003c\u003e\n      \u003cbutton onClick={() =\u003e router.push('/')}\u003eHome\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e router.back()}\u003eBack\u003c/button\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n## Error handling\n\nWaku sets up a default error boundary at the root of your application. You can customize error handling by adding your own error boundaries anywhere, for example with the [`react-error-boundary`](https://www.npmjs.com/package/react-error-boundary) library.\n\nWhen errors are thrown from server components or server functions, the errors are automatically replayed on browser. This allows the closest error boundaries to catch and handle these errors, even though they originated on the server.\n\n```tsx\n// ./src/pages/index.tsx\nimport { ErrorBoundary } from 'react-error-boundary';\n\nexport default async function HomePage() {\n  return (\n    \u003c\u003e\n      \u003cErrorBoundary fallback={\u003cdiv\u003eCaught server component error!\u003c/div\u003e}\u003e\n        \u003cThrowComponent /\u003e\n      \u003c/ErrorBoundary\u003e\n      \u003cErrorBoundary fallback={\u003cdiv\u003eCaught server function error!\u003c/div\u003e}\u003e\n        \u003cform\n          action={async () =\u003e {\n            'use server';\n            throw new Error('Oops!');\n          }}\n        \u003e\n          \u003cbutton\u003eCrash\u003c/button\u003e\n        \u003c/form\u003e\n      \u003c/ErrorBoundary\u003e\n    \u003c/\u003e\n  );\n}\n\nconst ThrowComponent = async () =\u003e {\n  throw new Error('Oops!');\n  return \u003c\u003e...\u003c/\u003e;\n};\n```\n\nError boundaries handle **unexpected errors** as a last resort safety net. For expected error conditions (like validation or network failures), handle them explicitly in your application logic.\n\nIn production, server errors are automatically obfuscated on the client to avoid revealing server internals. Detailed error messages and stack traces are only visible in development.\n\nIf you customize the root element (see [Root element](#root-element)), you should add your own error boundary there, as Waku's default root error boundary is included in the default root element.\n\n## Metadata\n\nWaku automatically hoists any title, meta, and link tags to the document head. That means adding meta tags is as simple as adding them to any of your layout or page components.\n\n```tsx\n// ./src/pages/_layout.tsx\nexport default async function RootLayout({ children }) {\n  return (\n    \u003c\u003e\n      \u003clink rel=\"icon\" type=\"image/png\" href=\"/images/favicon.png\" /\u003e\n      \u003cmeta property=\"og:image\" content=\"/images/opengraph.png\" /\u003e\n      {children}\n    \u003c/\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n```tsx\n// ./src/pages/index.tsx\nexport default async function HomePage() {\n  return (\n    \u003c\u003e\n      \u003ctitle\u003eWaku\u003c/title\u003e\n      \u003cmeta name=\"description\" content=\"The minimal React framework\" /\u003e\n      \u003ch1\u003eWaku\u003c/h1\u003e\n      \u003cdiv\u003eHello world!\u003c/div\u003e\n    \u003c/\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\nMetadata can also be generated programmatically.\n\n```tsx\n// ./src/pages/index.tsx\nexport default async function HomePage() {\n  return (\n    \u003c\u003e\n      \u003cHead /\u003e\n      \u003cdiv\u003e{/* ...*/}\u003c/div\u003e\n    \u003c/\u003e\n  );\n}\n\nconst Head = async () =\u003e {\n  const metadata = await getMetadata();\n\n  return (\n    \u003c\u003e\n      \u003ctitle\u003e{metadata.title}\u003c/title\u003e\n      \u003cmeta name=\"description\" content={metadata.description} /\u003e\n    \u003c/\u003e\n  );\n};\n\nconst getMetadata = async () =\u003e {\n  /* ... */\n};\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n## Styling\n\n### Global styles\n\nInstall any required dev dependencies (e.g., `npm i -D tailwindcss @tailwindcss/vite`) and set up any required configuration (e.g., `waku.config.ts`). Then create your global stylesheet (e.g., `./src/styles.css`) and import it into the root layout.\n\n```tsx\n// ./src/pages/_layout.tsx\nimport '../styles.css';\n\nexport default async function RootLayout({ children }) {\n  return \u003c\u003e{children}\u003c/\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n```css\n/* ./src/styles.css */\n@import 'tailwindcss';\n```\n\n```js\n// ./waku.config.ts\nimport { defineConfig } from 'waku/config';\nimport tailwindcss from '@tailwindcss/vite';\n\nexport default defineConfig({\n  vite: {\n    plugins: [tailwindcss()],\n  },\n});\n```\n\n## Static assets\n\nStatic assets such as images, fonts, stylesheets, and scripts can be placed in a special `./public` folder of the Waku project root directory. The public directory structure is served relative to the `/` base path.\n\n```tsx\n// assuming image is saved at `/public/images/logo.svg`\n\nexport const Logo = () =\u003e {\n  return (\n    \u003c\u003e\n      \u003cimg src=\"/images/logo.svg\" /\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n## File system\n\nFiles placed in a special `./private` folder of the Waku project root directory can be securely accessed in React server components.\n\n```tsx\nexport default async function HomePage() {\n  const file = readFileSync('./private/README.md', 'utf8');\n\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n}\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n## Data fetching\n\n### Server\n\nAll of the wonderful patterns enabled by React server components are supported. For example, you can compile MDX files or perform code syntax highlighting on the server with zero impact on the client bundle size.\n\n```tsx\n// ./src/pages/blog/[slug].tsx\nimport type { PageProps } from 'waku/router';\n\nimport { MDX } from '../../components/mdx';\nimport { getArticle, getStaticPaths } from '../../lib/blog';\n\nexport default async function BlogArticlePage({\n  slug,\n}: PageProps\u003c'/blog/[slug]'\u003e) {\n  const article = await getArticle(slug);\n\n  return (\n    \u003c\u003e\n      \u003ctitle\u003e{article.frontmatter.title}\u003c/title\u003e\n      \u003ch1\u003e{article.frontmatter.title}\u003c/h1\u003e\n      \u003cMDX\u003e{article.content}\u003c/MDX\u003e\n    \u003c/\u003e\n  );\n}\n\nexport const getConfig = async () =\u003e {\n  const staticPaths = await getStaticPaths();\n\n  return {\n    render: 'static',\n    staticPaths,\n  } as const;\n};\n```\n\n### Client\n\nData should be fetched on the server when possible for the best user experience, but all data fetching libraries such as React Query are compatible with Waku.\n\n## Mutations\n\nData mutations can be performed via [server actions](https://react.dev/reference/rsc/server-actions) or API endpoints.\n\n### API endpoints\n\nCreate API routes by making a new file in the special `./src/pages/_api` directory and exporting one or more functions named after the HTTP methods that you want it to support: `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, `TRACE`, or `PATCH`. The name of the file determines the route it will be served from. Each function receives a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a standard [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object.\n\n```ts\n// ./src/pages/_api/contact.ts\nimport emailClient from 'some-email';\n\nconst client = new emailClient(process.env.EMAIL_API_TOKEN!);\n\nexport const POST = async (request: Request): Promise\u003cResponse\u003e =\u003e {\n  const body = await request.json();\n\n  if (!body.message) {\n    return Response.json({ message: 'Invalid' }, { status: 400 });\n  }\n\n  try {\n    await client.sendEmail({\n      From: 'noreply@example.com',\n      To: 'someone@example.com',\n      Subject: 'Contact form submission',\n      Body: body.message,\n    });\n\n    return Response.json({ message: 'Success' }, { status: 200 });\n  } catch (error) {\n    return Response.json({ message: 'Failure' }, { status: 500 });\n  }\n};\n```\n\nAlternatively, you may export a default function as a \"catch-all\" handler that responds to all request methods.\n\n```ts\n// ./src/pages/_api/other-endpoint.ts\nexport default function handler(request: Request): Response {\n  return Response.json(\n    { message: 'Default handler ' + request.method },\n    { status: 200 },\n  );\n}\n```\n\n#### Calling API routes\n\nAPI routes are accessible at paths with the `_api` prefix stripped. For example a file at `./src/pages/_api/contact.ts` is available at `/contact`, and `./src/pages/_api/blog/rss.xml.ts` is available at `/blog/rss.xml`. You can call these endpoints from your client components using the standard [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) method.\n\n```tsx\n'use client';\n\nimport { useState } from 'react';\n\nexport const ContactForm = () =\u003e {\n  const [message, setMessage] = useState('');\n  const [status, setStatus] = useState('idle');\n\n  const handleSubmit = async (event) =\u003e {\n    event.preventDefault();\n    setStatus('sending');\n\n    try {\n      const response = await fetch('/contact', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({ message }),\n      });\n\n      const data = await response.json();\n\n      if (response.status === 200) {\n        setStatus('success');\n        setMessage('');\n      } else {\n        setStatus('error');\n        console.error('Error:', data.message);\n      }\n    } catch (error) {\n      setStatus('error');\n      console.error('Error:', error);\n    }\n  };\n\n  return (\n    \u003cform onSubmit={handleSubmit}\u003e\n      \u003ctextarea\n        value={message}\n        onChange={(event) =\u003e setMessage(event.target.value)}\n        placeholder=\"Your message...\"\n        required\n      /\u003e\n      \u003cbutton type=\"submit\" disabled={status === 'sending'}\u003e\n        {status === 'sending' ? 'Sending...' : 'Send Message'}\n      \u003c/button\u003e\n      {status === 'success' \u0026\u0026 \u003cp\u003eMessage sent!\u003c/p\u003e}\n      {status === 'error' \u0026\u0026 \u003cp\u003eFailed. Please try again.\u003c/p\u003e}\n    \u003c/form\u003e\n  );\n};\n```\n\n#### Configuring API routes\n\nAPI routes are dynamic by default, but if you’re using them to create a static resource such as an XML document, you can export a `getConfig` function that returns a config object with the render property set to `'static'`.\n\n```ts\n// ./src/pages/_api/blog/rss.xml.ts\n\nexport const GET = async () =\u003e {\n  const rss = generateRSSFeed(); // your RSS generation logic\n\n  return new Response(rss, {\n    headers: { 'Content-Type': 'application/rss+xml' },\n  });\n};\n\nexport const getConfig = async () =\u003e {\n  return {\n    render: 'static',\n  } as const;\n};\n```\n\n### Server actions\n\nServer actions allow you to define and securely execute server-side logic directly from your React components without the need for manually setting up API endpoints, sending `POST` requests to them with `fetch`, or managing pending states and errors.\n\n#### Defining and protecting actions\n\nThe `'use server'` directive marks an async function as a server action. Waku automatically creates a reference to the action that can be passed as props or imported into client components, which can then call the referenced function.\n\nWhen the directive is placed at the top of a function body, it will mark that specific function as an action. Alternatively, the directive can be placed at the top of a file, which will mark _all_ exported functions as actions at once.\n\nBe careful not to add the directive where inappropriate and inadvertently create unwanted endpoints. Endpoints created by server actions are _not_ secured unless you add your own authentication and authorization logic inside the function body.\n\n\u003e The `'use server'` directive has no relation to the`'use client'` directive. It does **not** mark a component as a server component and should **not** be placed at the top of server components!\n\n#### Making and consuming actions\n\nWhen creating an inline server action within a server component, it can be passed as props to a client component.\n\n```tsx\n// ./src/pages/contact.tsx\n\nimport db from 'some-db';\n\nexport default async function ContactPage() {\n  const sendMessage = async (message: string) =\u003e {\n    'use server';\n    await db.messages.create(message);\n  };\n\n  return \u003cContactForm sendMessage={sendMessage} /\u003e;\n}\n```\n\n```tsx\n// ./src/components/contact-form.tsx\n'use client';\n\nimport { useState } from 'react';\n\nexport const ContactForm = ({ sendMessage }) =\u003e {\n  const [message, setMessage] = useState('');\n\n  return (\n    \u003c\u003e\n      \u003ctextarea\n        value={message}\n        onChange={(event) =\u003e setMessage(event.target.value)}\n        rows={4}\n      /\u003e\n      \u003cbutton onClick={() =\u003e sendMessage(message)}\u003eSend message\u003c/button\u003e\n    \u003c/\u003e\n  );\n};\n```\n\nWhen creating server actions in a separate file, they can be imported directly into client components.\n\n\u003e When using a top-level `'use server'` directive, note that _all_ exported functions will be made into API endpoints. So be careful only to export functions intended for this purpose. Add server-side logic to validate proper authentication and authorization if appropriate.\n\n```tsx\n// ./src/actions/send-message.ts\n'use server';\n\nimport db from 'some-db';\n\nexport async function sendMessage(message: string) {\n  await db.messages.create(message);\n}\n```\n\n```tsx\n// ./src/components/contact-button.tsx\n'use client';\n\nimport { sendMessage } from '../actions/send-message';\n\nexport const ContactButton = () =\u003e {\n  const message = `Hello world!`;\n\n  return \u003cbutton onClick={() =\u003e sendMessage(message)}\u003eSend message\u003c/button\u003e;\n};\n```\n\n#### Invoking actions\n\nActions can be invoked via event handlers such as `onClick` or `onSubmit`, as in the examples above, or in a `useEffect` hook, based on whichever conditions you choose.\n\nThey can also be invoked via an `action` prop on native `\u003cform\u003e` elements. In this case the server action will automatically receive a parameter of `FormData` with all of the form field values, including hidden ones.\n\n```tsx\n// ./src/actions/send-message.ts\n'use server';\n\nimport db from 'some-db';\n\nexport async function sendMessage(formData: FormData) {\n  const message = formData.get('message');\n\n  await db.messages.create(message);\n}\n```\n\n```tsx\n// ./src/components/create-todo-button.tsx\n'use client';\n\nimport { sendMessage } from '../actions/send-message';\n\nexport const ContactForm = () =\u003e {\n  return (\n    \u003cform action={sendMessage}\u003e\n      \u003ctextarea name=\"message\" rows={4} /\u003e\n      \u003cinput type=\"hidden\" name=\"secret-message\" value=\"This too!\" /\u003e\n      \u003cbutton type=\"submit\"\u003eSend message\u003c/button\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\nIf you must pass additional arguments to a form action beyond its native form fields, you can use the `bind` method to create an extended server action with the extra arguments.\n\n```tsx\n// ./src/components/create-todo-button.tsx\n'use client';\n\nimport { sendMessage } from '../actions/send-message';\n\nexport const ContactForm = ({ author = 'guest' }) =\u003e {\n  const sendMessageWithAuthor = sendMessage.bind(null, author);\n\n  return (\n    \u003cform action={sendMessageWithAuthor}\u003e\n      \u003ctextarea name=\"message\" rows={4} /\u003e\n      \u003cbutton type=\"submit\"\u003eSend message\u003c/button\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\n#### Enhancing actions\n\nServer actions integrate with many other React APIs such as the [`useTransition`](https://react.dev/reference/react/useTransition) hook for handling pending states, the [`useActionState`](https://react.dev/reference/react/useActionState) hook for accessing returned values, and the [`useOptimistic`](https://react.dev/reference/react/useOptimistic) hook for performing optimistic UI updates.\n\nSee the talk [What’s new in React 19?](https://www.youtube.com/watch?v=AJOGzVygGcY) to learn more.\n\n## State management\n\nWe recommend [Jotai](https://jotai.org) for global React state management based on the atomic model’s performance and scalability, but Waku is compatible with all React state management libraries such as Zustand and Valtio.\n\n\u003e We’re exploring a deeper integration of atomic state management into Waku to achieve the performance and developer experience of signals while preserving React’s declarative programming model.\n\n## Environment variables\n\nIt’s important to distinguish environment variables that must be kept secret from those that can be made public.\n\n#### Private\n\nBy default all environment variables are considered private and are accessible only in server components, which can be rendered exclusively in a secure environment. You must still take care not to inadvertently pass the variable as props to any client components.\n\n#### Public\n\nA special `WAKU_PUBLIC_` prefix is required to make an environment variable public and accessible in client components. They will be present as cleartext in the production JavaScript bundle sent to users’ browsers.\n\n### Runtime agnostic (recommended)\n\nEnvironment variables are available on the server via the Waku `getEnv` function and on the client via `import.meta.env`.\n\n```tsx\n// server components can access both private and public variables\nimport { getEnv } from 'waku';\n\nexport const ServerComponent = async () =\u003e {\n  const secretKey = getEnv('SECRET_KEY');\n\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n};\n```\n\n```tsx\n// client components can only access public variables\n'use client';\n\nexport const ClientComponent = () =\u003e {\n  const publicStatement = import.meta.env.WAKU_PUBLIC_HELLO;\n\n  return \u003c\u003e{/* ...*/}\u003c/\u003e;\n};\n```\n\n### Node.js\n\nIn Node.js environments, `process.env` may also be used.\n\n## Deployment\n\n### Node.js (default)\n\nAfter building, `waku start` runs the production server.\n\nIf you need a standalone app (for example, for Docker),\nthe build output lives in `dist`. It is the only folder you need to copy, then run `node dist/serve-node.js`.\n\n### Pure SSG\n\nThe build output for SSG lives in `dist/public`. You can copy (or upload to any hosting service) the `dist/public` folder.\nWith Pure SSG, dynamic features (like dynamic rendering, server actions, API routes) do not work.\n\n### Vercel\n\nWaku projects can be deployed to Vercel with the [Vercel CLI](https://vercel.com/docs/cli) automatically.\n\n```sh\nvercel\n```\n\n#### Pure SSG with Vercel\n\nFor advanced users who want to avoid deploying functions, use the server entry file with the Vercel adapter and specify the `static` option.\n\n`./src/waku.server.ts`:\n\n```ts\nimport { fsRouter } from 'waku';\nimport adapter from 'waku/adapters/vercel';\n\nexport default adapter(\n  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),\n  { static: true },\n);\n```\n\n### Netlify\n\nWaku projects can be deployed to Netlify with the [Netlify CLI](https://docs.netlify.com/cli/get-started/).\n\n```sh\nNETLIFY=1 npm run build\nnetlify deploy\n```\n\n#### Pure SSG with Netlify\n\nFor advanced users who want to avoid deploying functions, use the server entry file with the Netlify adapter and specify the `static` option.\n\n`./src/waku.server.ts`:\n\n```ts\nimport { fsRouter } from 'waku';\nimport adapter from 'waku/adapters/netlify';\n\nexport default adapter(\n  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),\n  { static: true },\n);\n```\n\n### Cloudflare Workers\n\nWaku projects can be deployed to Cloudflare Workers with [Wrangler](https://developers.cloudflare.com/workers/wrangler/).\n\n```sh\nCLOUDFLARE=1 npm run build\nwrangler deploy\n```\n\n#### Pure SSG with Cloudflare Workers\n\n`./src/waku.server.ts`:\n\n```ts\nimport { fsRouter } from 'waku';\nimport adapter from 'waku/adapters/cloudflare';\n\nexport default adapter(\n  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),\n  { static: true },\n);\n```\n\n### Deno Deploy (experimental)\n\n`./src/waku.server.ts`:\n\n```ts\nimport { fsRouter } from 'waku';\nimport adapter from 'waku/adapters/deno';\n\nexport default adapter(\n  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),\n);\n```\n\n```sh\nnpm run build\ndeployctl deploy --prod dist/serve-deno.js --exclude node_modules\n```\n\n### AWS Lambda (experimental)\n\n`./src/waku.server.ts`:\n\n```ts\nimport { fsRouter } from 'waku';\nimport adapter from 'waku/adapters/aws-lambda';\n\nexport default adapter(\n  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),\n  streaming: false, // optional, default is false\n);\n```\n\n```sh\nnpm run build\n```\n\nThe handler entrypoint is `dist/serve-asw-lambda.js`: see [Hono AWS Lambda Deploy Docs](https://hono.dev/getting-started/aws-lambda#_3-deploy).\n\n### Edge\n\n`waku/adapters/edge` adapter provides a minimal server output without deployment target specific code. For example, you can use it with [Nitro](https://nitro.build/) to handle packaging for various deployment platforms. See [waku-nitro-example](https://github.com/hi-ogawa/waku-nitro-example) for the example.\n\n```ts\n// [waku.config.ts]\nimport { defineConfig } from 'waku/config';\n\nexport default defineConfig({\n  unstable_adapter: 'waku/adapters/edge',\n});\n```\n\n## Community\n\nPlease join our friendly [GitHub discussions](https://github.com/wakujs/waku/discussions) or [Discord server](https://discord.gg/MrQdmzd) to participate in the Waku community. Hope to see you there!\n\n## Roadmap\n\nWaku is in active development and we’re seeking additional contributors. Check out our [roadmap](https://github.com/wakujs/waku/issues/24) for more information.\n\n## Contributing\n\nIf you would like to contribute, please see [CONTRIBUTING.md](https://github.com/wakujs/waku/blob/main/CONTRIBUTING.md)!\n","funding_links":["https://github.com/sponsors/dai-shi"],"categories":["🌐 Web Development - Frontend","TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwakujs%2Fwaku","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwakujs%2Fwaku","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwakujs%2Fwaku/lists"}