Introduction

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.

Note

Please try Waku on non-production projects and report any issues you find. Contributors are welcome.

Getting started

Start a new Waku project with the create command for your preferred package manager. It will scaffold a new project with our default Waku starter.

npm create waku@latest

Commands

  • waku dev to start the local development server
  • waku build to generate a production build
  • waku start to serve the production build locally

Node.js version requirement: ^24.0.0 or ^22.12.0 or ^20.19.0

Rendering

While 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.

So 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.

And 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.

Note

Future versions of Waku may provide additional opt-in APIs to abstract some of the complexity away for an improved developer experience.

Server components

Server 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.

// server component
import db from 'some-db';

import { Gallery } from '../components/gallery';

export const Store = async () => {
  const products = await db.query('SELECT * FROM products');

  return <Gallery products={products} />;
};

Client components

A '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.

// client component
'use client';

import { useState } from 'react';

export const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      <div>Count: {count}</div>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
    </>
  );
};

Shared components

Simple React components that meet all of the rules of both server and client components can be imported into either server or client components without affecting the server-client boundary.

// shared component
export const Headline = ({ children }) => {
  return <h3>{children}</h3>;
};

Weaving patterns

Server 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.

// ./src/pages/_layout.tsx
import { Providers } from '../components/providers';

export default async function RootLayout({ children }) {
  return (
    <Providers>
      <main>{children}</main>
    </Providers>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};
// ./src/components/providers.tsx
'use client';

import { Provider } from 'jotai';

export const Providers = ({ children }) => {
  return <Provider>{children}</Provider>;
};

Server-side rendering

Waku 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.

tl;dr:

Each layout and page in Waku is composed of a React component hierarchy.

It 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.

Server components can be rendered below this boundary, but only via composition (e.g., children props). Together they form a new “React server” layer that runs before the traditional “React client” layer with which you’re already familiar.

Client components are still server-side rendered as SSR is separate from RSC. Please see the linked diagrams for a helpful visual.

Further reading

To learn more about the modern React architecture, we recommend Making Sense of React Server Components and The Two Reacts.

Routing

The directory for file-based routing in Waku projects is ./src/pages.

Layouts 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.

Waku currently supports two rendering options:

  • 'static' for static prerendering (SSG)

  • 'dynamic' for server-side rendering (SSR)

Layouts, pages, and slices are all static by default, while api handlers default to dynamic.

For 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.

// ./src/pages/_layout.tsx
import '../styles.css';

import { Providers } from '../components/providers';
import { Header } from '../components/header';
import { Footer } from '../components/footer';

// Create root layout
export default async function RootLayout({ children }) {
  return (
    <Providers>
      <Header />
      <main>{children}</main>
      <Footer />
    </Providers>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};
// ./src/pages/index.tsx

// Create home page
export default async function HomePage() {
  const data = await getData();

  return (
    <>
      <h1>{data.title}</h1>
      <div>{data.content}</div>
    </>
  );
}

const getData = async () => {
  /* ... */
};

export const getConfig = async () => {
  return {
    render: 'dynamic',
  } as const;
};

Pages

Pages 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).

Single routes

Pages can be rendered as a single route (e.g., about.tsx or blog/index.tsx).

// ./src/pages/about.tsx

// Create about page
export default async function AboutPage() {
  return <>{/* ...*/}</>;
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};
// ./src/pages/blog/index.tsx

// Create blog index page
export default async function BlogIndexPage() {
  return <>{/* ...*/}</>;
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

Segment routes

Segment routes (e.g., [slug].tsx or [slug]/index.tsx) are marked with brackets.

The 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').

If statically prerendering a segment route at build time, a staticPaths array must also be provided.

// ./src/pages/blog/[slug].tsx
import type { PageProps } from 'waku/router';

// Create blog article pages
export default async function BlogArticlePage({
  slug,
}: PageProps<'/blog/[slug]'>) {
  const data = await getData(slug);

  return <>{/* ...*/}</>;
}

const getData = async (slug) => {
  /* ... */
};

export const getConfig = async () => {
  return {
    render: 'static',
    staticPaths: ['introducing-waku', 'introducing-pages-router'],
  } as const;
};
// ./src/pages/shop/[category].tsx
import type { PageProps } from 'waku/router';

// Create product category pages
export default async function ProductCategoryPage({
  category,
}: PageProps<'/shop/[category]'>) {
  const data = await getData(category);

  return <>{/* ...*/}</>;
}

const getData = async (category) => {
  /* ... */
};

export const getConfig = async () => {
  return {
    render: 'dynamic',
  } as const;
};

Static paths (or other config values) can also be generated programmatically.

// ./src/pages/blog/[slug].tsx
import type { PageProps } from 'waku/router';

// Create blog article pages
export default async function BlogArticlePage({
  slug,
}: PageProps<'/blog/[slug]'>) {
  const data = await getData(slug);

  return <>{/* ...*/}</>;
}

const getData = async (slug) => {
  /* ... */
};

export const getConfig = async () => {
  const staticPaths = await getStaticPaths();

  return {
    render: 'static',
    staticPaths,
  } as const;
};

const getStaticPaths = async () => {
  /* ... */
};

Nested segment routes

Routes can contain multiple segments (e.g., /shop/[category]/[product]) by creating folders with brackets as well.

// ./src/pages/shop/[category]/[product].tsx
import type { PageProps } from 'waku/router';

// Create product category pages
export default async function ProductDetailPage({
  category,
  product,
}: PageProps<'/shop/[category]/[product]'>) {
  return <>{/* ...*/}</>;
}

export const getConfig = async () => {
  return {
    render: 'dynamic',
  } as const;
};

For static prerendering of nested segment routes, the staticPaths array is instead composed of ordered arrays.

// ./src/pages/shop/[category]/[product].tsx
import type { PageProps } from 'waku/router';

// Create product detail pages
export default async function ProductDetailPage({
  category,
  product,
}: PageProps<'/shop/[category]/[product]'>) {
  return <>{/* ...*/}</>;
}

export const getConfig = async () => {
  return {
    render: 'static',
    staticPaths: [
      ['same-category', 'some-product'],
      ['same-category', 'another-product'],
    ],
  } as const;
};

Catch-all routes

Catch-all or “wildcard” segment routes (e.g., /app/[...catchAll]) are marked with an ellipsis before the name and have indefinite segments.

Wildcard 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.

// ./src/pages/app/[...catchAll].tsx
import type { PageProps } from 'waku/router';

// Create dashboard page
export default async function DashboardPage({
  catchAll,
}: PageProps<'/app/[...catchAll]'>) {
  return <>{/* ...*/}</>;
}

export const getConfig = async () => {
  return {
    render: 'dynamic',
  } as const;
};

Group routes

Group 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.

For 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:

├── (main)
│ ├── _layout.tsx
│ ├── about.tsx
│ └── contact.tsx
└── index.tsx

In this structure, /about and /contact will use the layout from (main)/_layout.tsx, but / (from index.tsx) will not.

// ./src/pages/(main)/_layout.tsx
import { Header } from '../../components/header';
import { Footer } from '../../components/footer';

// Create shared layout for main pages
export default async function MainLayout({ children }) {
  return (
    <>
      <Header />
      <main>{children}</main>
      <Footer />
    </>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};
// ./src/pages/(main)/about.tsx
export default async function AboutPage() {
  return <h1>About Us</h1>;
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

Group 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:

(main)
├── (dynamic)
│ ├── _layout.tsx # dynamic layout
│ ├── dashboard.tsx
│ └── profile.tsx
└── _layout.tsx # static layout

This allows for fine-grained control over rendering modes - some work can be done at build time (static) while other work happens at runtime (dynamic).

// ./src/pages/(main)/_layout.tsx
// Static layout - runs at build time
export default async function MainLayout({ children }) {
  return <div className="main-container">{children}</div>;
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};
// ./src/pages/(main)/(dynamic)/_layout.tsx
// Dynamic layout - runs at request time
export default async function DynamicLayout({ children }) {
  const userData = await fetchUserData(); // Dynamic data fetching

  return (
    <div className="dynamic-container">
      <UserContext.Provider value={userData}>{children}</UserContext.Provider>
    </div>
  );
}

export const getConfig = async () => {
  return {
    render: 'dynamic',
  } as const;
};

Group routes are especially powerful for organizing complex applications where different sections need different layouts, state management, or data requirements while maintaining clean URLs.

Ignored routes

The following directories are ignored by the router:

  • _components
  • _hooks

All files inside these directories are excluded from routing.

For instance, in the case below, pages/about.tsx is routed to /about, but files like _components/header.tsx are not routed anywhere.

pages/
├── about.tsx
├── _components/
│   ├── header.tsx   // 👈🏼 ignored
│   ├── footer.tsx   // 👈🏼 ignored
│   ├── ...          // 👈🏼 ignored

Router paths type safety

Import 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:

PageProps<'/blog/[slug]'>;
// => { path: string; slug: string; query: string }

PageProps<'/shop/[category]/[product]'>;
// => { path: string; category: string; product: string; query: string }

Layouts

Layouts 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.

Root layout

The 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.

// ./src/pages/_layout.tsx
import '../styles.css';

import { Providers } from '../components/providers';
import { Header } from '../components/header';
import { Footer } from '../components/footer';

// Create root layout
export default async function RootLayout({ children }) {
  return (
    <Providers>
      <link rel="icon" type="image/png" href="/images/favicon.png" />
      <meta property="og:image" content="/images/opengraph.png" />
      <Header />
      <main>{children}</main>
      <Footer />
    </Providers>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};
// ./src/components/providers.tsx
'use client';

import { createStore, Provider } from 'jotai';

const store = createStore();

export const Providers = ({ children }) => {
  return <Provider store={store}>{children}</Provider>;
};

Other layouts

Layouts 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.

// ./src/pages/blog/_layout.tsx
import { Sidebar } from '../../components/sidebar';

// Create blog layout
export default async function BlogLayout({ children }) {
  return (
    <div className="flex">
      <div>{children}</div>
      <Sidebar />
    </div>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

Root element

The attributes of <html>, <head>, or <body> 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.

// ./src/pages/_root.tsx

// Create root element
export default async function RootElement({ children }) {
  return (
    <html lang="en">
      <head></head>
      <body data-version="1.0">{children}</body>
    </html>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

Slices

Slices 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.

Creating slices

Slices 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.

src/pages
 ├── _slices
 │   ├── one.tsx
 │   ├── two.tsx
 │   └── nested
 │       └── three.tsx
 └── some-page.tsx

Each slice file exports a default React component and a getConfig function that specifies the render method.

// ./src/pages/_slices/one.tsx

// Create slice component
export default function SliceOne() {
  return <p>🍕</p>;
}

export const getConfig = () => {
  return {
    render: 'static', // default is 'static'
  };
};
// ./src/pages/_slices/nested/three.tsx

// Create nested slice component
export default function SliceThree() {
  return <p>🍰</p>;
}

export const getConfig = () => {
  return {
    render: 'dynamic',
  };
};

Using slices

Slices 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.

// ./src/pages/some-page.tsx
import { Slice } from 'waku';

// Create page with slices
export default function SomePage() {
  return (
    <div>
      <Slice id="one" />
      <Slice id="two" />
      <Slice id="nested/three" />
    </div>
  );
}

export const getConfig = () => {
  return {
    render: 'static',
    slices: ['one', 'two', 'nested/three'],
  };
};

Lazy slices

Lazy 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.

Lazy slices are marked with the lazy prop and can include a fallback component to display while loading.

// ./src/pages/some-page.tsx
import { Slice } from 'waku';

// Create page with lazy slice
export default function SomePage() {
  return (
    <div>
      <Slice id="one" />
      <Slice id="two" lazy fallback={<p>Two is loading...</p>} />
    </div>
  );
}

export const getConfig = () => {
  return {
    render: 'static',
    slices: ['one'], // Note: 'two' is lazy, so it is not included
  };
};

This allows you to have a dynamic slice component while keeping the rest of the page static.

The <Link /> component should be used for internal links. It accepts a to prop for the destination, which is automatically prefetched ahead of the navigation.

// ./src/pages/index.tsx
import { Link } from 'waku';

export default async function HomePage() {
  return (
    <>
      <h1>Home</h1>
      <Link to="/about">About</Link>
    </>
  );
}

useRouter

The useRouter hook can be used to inspect the current route or perform programmatic navigation.

router properties

The router object has two properties related to the current route: path (string) and query (string).

'use client';

import { useRouter } from 'waku';

export const Component = () => {
  const { path, query } = useRouter();

  return (
    <>
      <div>current path: {path}</div>
      <div>current query: {query}</div>
    </>
  );
};

router methods

The router object also contains several methods for programmatic navigation:

  • router.push(to: string) - navigate to the provided route

  • router.prefetch(to: string) - prefetch the provided route

  • router.replace(to: string) - replace the current history entry

  • router.reload() - reload the current route

  • router.back() - navigate to the previous entry in the session history

  • router.forward() - navigate to the next entry in the session history

'use client';

import { useRouter } from 'waku';

export const Component = () => {
  const router = useRouter();

  return (
    <>
      <button onClick={() => router.push('/')}>Home</button>
      <button onClick={() => router.back()}>Back</button>
    </>
  );
};

Error handling

Waku 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 library.

When 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.

// ./src/pages/index.tsx
import { ErrorBoundary } from 'react-error-boundary';

export default async function HomePage() {
  return (
    <>
      <ErrorBoundary fallback={<div>Caught server component error!</div>}>
        <ThrowComponent />
      </ErrorBoundary>
      <ErrorBoundary fallback={<div>Caught server function error!</div>}>
        <form
          action={async () => {
            'use server';
            throw new Error('Oops!');
          }}
        >
          <button>Crash</button>
        </form>
      </ErrorBoundary>
    </>
  );
}

const ThrowComponent = async () => {
  throw new Error('Oops!');
  return <>...</>;
};

Error 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.

In 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.

If you customize the root element (see Root element), you should add your own error boundary there, as Waku's default root error boundary is included in the default root element.

Metadata

Waku 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.

// ./src/pages/_layout.tsx
export default async function RootLayout({ children }) {
  return (
    <>
      <link rel="icon" type="image/png" href="/images/favicon.png" />
      <meta property="og:image" content="/images/opengraph.png" />
      {children}
    </>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};
// ./src/pages/index.tsx
export default async function HomePage() {
  return (
    <>
      <title>Waku</title>
      <meta name="description" content="The minimal React framework" />
      <h1>Waku</h1>
      <div>Hello world!</div>
    </>
  );
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

Metadata can also be generated programmatically.

// ./src/pages/index.tsx
export default async function HomePage() {
  return (
    <>
      <Head />
      <div>{/* ...*/}</div>
    </>
  );
}

const Head = async () => {
  const metadata = await getMetadata();

  return (
    <>
      <title>{metadata.title}</title>
      <meta name="description" content={metadata.description} />
    </>
  );
};

const getMetadata = async () => {
  /* ... */
};

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

Styling

Global styles

Install 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.

// ./src/pages/_layout.tsx
import '../styles.css';

export default async function RootLayout({ children }) {
  return <>{children}</>;
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};
/* ./src/styles.css */
@import 'tailwindcss';
// ./waku.config.ts
import { defineConfig } from 'waku/config';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
  },
});

Static assets

Static 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.

// assuming image is saved at `/public/images/logo.svg`

export const Logo = () => {
  return (
    <>
      <img src="/images/logo.svg" />
    </>
  );
};

File system

Files placed in a special ./private folder of the Waku project root directory can be securely accessed in React server components.

export default async function HomePage() {
  const file = readFileSync('./private/README.md', 'utf8');

  return <>{/* ...*/}</>;
}

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

Data fetching

Server

All 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.

// ./src/pages/blog/[slug].tsx
import type { PageProps } from 'waku/router';

import { MDX } from '../../components/mdx';
import { getArticle, getStaticPaths } from '../../lib/blog';

export default async function BlogArticlePage({
  slug,
}: PageProps<'/blog/[slug]'>) {
  const article = await getArticle(slug);

  return (
    <>
      <title>{article.frontmatter.title}</title>
      <h1>{article.frontmatter.title}</h1>
      <MDX>{article.content}</MDX>
    </>
  );
}

export const getConfig = async () => {
  const staticPaths = await getStaticPaths();

  return {
    render: 'static',
    staticPaths,
  } as const;
};

Client

Data 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.

Mutations

Data mutations can be performed via server actions or API endpoints.

API endpoints

Create 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 object and returns a standard Response object.

// ./src/pages/_api/contact.ts
import emailClient from 'some-email';

const client = new emailClient(process.env.EMAIL_API_TOKEN!);

export const POST = async (request: Request): Promise<Response> => {
  const body = await request.json();

  if (!body.message) {
    return Response.json({ message: 'Invalid' }, { status: 400 });
  }

  try {
    await client.sendEmail({
      From: '[email protected]',
      To: '[email protected]',
      Subject: 'Contact form submission',
      Body: body.message,
    });

    return Response.json({ message: 'Success' }, { status: 200 });
  } catch (error) {
    return Response.json({ message: 'Failure' }, { status: 500 });
  }
};

Alternatively, you may export a default function as a "catch-all" handler that responds to all request methods.

// ./src/pages/_api/other-endpoint.ts
export default function handler(request: Request): Response {
  return Response.json(
    { message: 'Default handler ' + request.method },
    { status: 200 },
  );
}

Typed route parameters

For API routes with dynamic segments (e.g., ./src/pages/_api/users/[id].ts), you can use ApiContext as the second parameter to get typed access to route parameters. The params property will be automatically typed based on the path pattern.

// ./src/pages/_api/users/[id].ts
import type { ApiContext } from 'waku/router';

export async function GET(
  _req: Request,
  { params }: ApiContext<'/users/[id]'>,
) {
  const { id } = params; // id is typed as string
  return Response.json({ id, message: `Hello user ${id}` });
}

This also works with multiple parameters and wildcard routes:

// ./src/pages/_api/files/[...path].ts
import type { ApiContext } from 'waku/router';

export async function GET(
  _req: Request,
  { params }: ApiContext<'/files/[...path]'>,
) {
  const { path } = params; // path is typed as string[]
  return Response.json({ segments: path });
}

Calling API routes

API 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 method.

'use client';

import { useState } from 'react';

export const ContactForm = () => {
  const [message, setMessage] = useState('');
  const [status, setStatus] = useState('idle');

  const handleSubmit = async (event) => {
    event.preventDefault();
    setStatus('sending');

    try {
      const response = await fetch('/contact', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ message }),
      });

      const data = await response.json();

      if (response.status === 200) {
        setStatus('success');
        setMessage('');
      } else {
        setStatus('error');
        console.error('Error:', data.message);
      }
    } catch (error) {
      setStatus('error');
      console.error('Error:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        value={message}
        onChange={(event) => setMessage(event.target.value)}
        placeholder="Your message..."
        required
      />
      <button type="submit" disabled={status === 'sending'}>
        {status === 'sending' ? 'Sending...' : 'Send Message'}
      </button>
      {status === 'success' && <p>Message sent!</p>}
      {status === 'error' && <p>Failed. Please try again.</p>}
    </form>
  );
};

Configuring API routes

API 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'.

// ./src/pages/_api/blog/rss.xml.ts

export const GET = async () => {
  const rss = generateRSSFeed(); // your RSS generation logic

  return new Response(rss, {
    headers: { 'Content-Type': 'application/rss+xml' },
  });
};

export const getConfig = async () => {
  return {
    render: 'static',
  } as const;
};

Server actions

Server 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.

Defining and protecting actions

The '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.

When 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.

Be 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.

Note

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!

Making and consuming actions

When creating an inline server action within a server component, it can be passed as props to a client component.

// ./src/pages/contact.tsx

import db from 'some-db';

export default async function ContactPage() {
  const sendMessage = async (message: string) => {
    'use server';
    await db.messages.create(message);
  };

  return <ContactForm sendMessage={sendMessage} />;
}
// ./src/components/contact-form.tsx
'use client';

import { useState } from 'react';

export const ContactForm = ({ sendMessage }) => {
  const [message, setMessage] = useState('');

  return (
    <>
      <textarea
        value={message}
        onChange={(event) => setMessage(event.target.value)}
        rows={4}
      />
      <button onClick={() => sendMessage(message)}>Send message</button>
    </>
  );
};

When creating server actions in a separate file, they can be imported directly into client components.

Note

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.

// ./src/actions/send-message.ts
'use server';

import db from 'some-db';

export async function sendMessage(message: string) {
  await db.messages.create(message);
}
// ./src/components/contact-button.tsx
'use client';

import { sendMessage } from '../actions/send-message';

export const ContactButton = () => {
  const message = `Hello world!`;

  return <button onClick={() => sendMessage(message)}>Send message</button>;
};

Invoking actions

Actions 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.

They can also be invoked via an action prop on native <form> elements. In this case the server action will automatically receive a parameter of FormData with all of the form field values, including hidden ones.

// ./src/actions/send-message.ts
'use server';

import db from 'some-db';

export async function sendMessage(formData: FormData) {
  const message = formData.get('message');

  await db.messages.create(message);
}
// ./src/components/create-todo-button.tsx
'use client';

import { sendMessage } from '../actions/send-message';

export const ContactForm = () => {
  return (
    <form action={sendMessage}>
      <textarea name="message" rows={4} />
      <input type="hidden" name="secret-message" value="This too!" />
      <button type="submit">Send message</button>
    </form>
  );
};

If 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.

// ./src/components/create-todo-button.tsx
'use client';

import { sendMessage } from '../actions/send-message';

export const ContactForm = ({ author = 'guest' }) => {
  const sendMessageWithAuthor = sendMessage.bind(null, author);

  return (
    <form action={sendMessageWithAuthor}>
      <textarea name="message" rows={4} />
      <button type="submit">Send message</button>
    </form>
  );
};

Enhancing actions

Server actions integrate with many other React APIs such as the useTransition hook for handling pending states, the useActionState hook for accessing returned values, and the useOptimistic hook for performing optimistic UI updates.

See the talk What’s new in React 19? to learn more.

State management

We recommend Jotai 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.

Note

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.

Environment variables

It’s important to distinguish environment variables that must be kept secret from those that can be made public.

Private

By 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.

Public

A 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.

Environment variables are available on the server via the Waku getEnv function and on the client via import.meta.env.

// server components can access both private and public variables
import { getEnv } from 'waku';

export const ServerComponent = async () => {
  const secretKey = getEnv('SECRET_KEY');

  return <>{/* ...*/}</>;
};
// client components can only access public variables
'use client';

export const ClientComponent = () => {
  const publicStatement = import.meta.env.WAKU_PUBLIC_HELLO;

  return <>{/* ...*/}</>;
};

Node.js

In Node.js environments, process.env may also be used.

Deployment

Node.js (default)

After building, waku start runs the production server.

If you need a standalone app (for example, for Docker), the build output lives in dist. It is the only folder you need to copy, then run node dist/serve-node.js.

Pure SSG

The build output for SSG lives in dist/public. You can copy (or upload to any hosting service) the dist/public folder. With Pure SSG, dynamic features (like dynamic rendering, server actions, API routes) do not work.

Vercel

Waku projects can be deployed to Vercel with the Vercel CLI automatically.

vercel

Pure SSG with Vercel

For advanced users who want to avoid deploying functions, use the server entry file with the Vercel adapter and specify the static option.

./src/waku.server.tsx:

import { fsRouter } from 'waku';
import adapter from 'waku/adapters/vercel';

export default adapter(
  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),
  { static: true },
);

Netlify

Waku projects can be deployed to Netlify with the Netlify CLI.

NETLIFY=1 npm run build
netlify deploy

Pure SSG with Netlify

For advanced users who want to avoid deploying functions, use the server entry file with the Netlify adapter and specify the static option.

./src/waku.server.tsx:

import { fsRouter } from 'waku';
import adapter from 'waku/adapters/netlify';

export default adapter(
  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),
  { static: true },
);

Cloudflare Workers

Waku projects can be deployed to Cloudflare Workers with Wrangler.

CLOUDFLARE=1 npm run build
wrangler deploy

Pure SSG with Cloudflare Workers

./src/waku.server.tsx:

import { fsRouter } from 'waku';
import adapter from 'waku/adapters/cloudflare';

export default adapter(
  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),
  { static: true },
);

Deno Deploy (experimental)

./src/waku.server.tsx:

import { fsRouter } from 'waku';
import adapter from 'waku/adapters/deno';

export default adapter(
  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),
);
npm run build
deployctl deploy --prod dist/serve-deno.js --exclude node_modules

Bun (experimental)

./src/waku.server.tsx:

import { fsRouter } from 'waku';
import adapter from 'waku/adapters/bun';

export default adapter(
  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),
);
npm run build

AWS Lambda (experimental)

./src/waku.server.tsx:

import { fsRouter } from 'waku';
import adapter from 'waku/adapters/aws-lambda';

export default adapter(
  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),
  streaming: false, // optional, default is false
);
npm run build

The handler entrypoint is dist/serve-aws-lambda.js: see Hono AWS Lambda Deploy Docs.

Edge

waku/adapters/edge adapter provides a minimal server output without deployment target specific code. For example, you can use it with Nitro to handle packaging for various deployment platforms. See waku-nitro-example for the example.

// [waku.config.ts]
import { defineConfig } from 'waku/config';

export default defineConfig({
  unstable_adapter: 'waku/adapters/edge',
});

Community

Please join our friendly GitHub discussions or Discord server to participate in the Waku community. Hope to see you there!

Roadmap

Waku is in active development and we’re seeking additional contributors. Check out our roadmap for more information.

Contributing

If you would like to contribute, please see CONTRIBUTING.md!

designed bycandycode alternative graphic design web development agency San Diego
\"}],\", or \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}],\" elements can be customized with the root element API. Create a special \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"_root.tsx\"}],\" file in the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/pages\"}],\" directory that accepts a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"children\"}],\" prop of type \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"ReactNode\"}],\".\"]}]\n12e:Tc81,
// ./src/pages/_root.tsx\n\n// Create root element\nexport default async function RootElement({ children }) {\n  return (\n    <html lang=\"en\">\n      <head></head>\n      <body data-version=\"1.0\">{children}</body>\n    </html>\n  );\n}\n\nexport const getConfig = async () => {\n  return {\n    render: 'static',\n  } as const;\n};
75:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$12e\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n76:[\"$\",\"h3\",null,{\"id\":\"slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#slices\",\"children\":\"Slices\"}]}]\n77:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Slices are reusable components that are defined in the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"src/pages/_slices\"}],\" directory. They allow you to compose pages by assembling components like normal React components while specifying alternate rendering patterns.\"]}]\n78:[\"$\",\"h4\",null,{\"id\":\"creating-slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#creating-slices\",\"children\":\"Creating slices\"}]}]\n79:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Slices are created by placing files in the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"src/pages/_slices\"}],\" directory. The slice ID corresponds to the filename, and nested slices use the full path as the ID.\"]}]\n12f:T454,
src/pages\n ├── _slices\n │   ├── one.tsx\n │   ├── two.tsx\n │   └── nested\n │       └── three.tsx\n └── some-page.tsx
7a:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$12f\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n7b:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Each slice file exports a default React component and a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"getConfig\"}],\" function that specifies the render method.\"]}]\n130:T74a,
// ./src/pages/_slices/one.tsx\n\n// Create slice component\nexport default function SliceOne() {\n  return <p>🍕</p>;\n}\n\nexport const getConfig = () => {\n  return {\n    render: 'static', // default is 'static'\n  };\n};
7c:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$130\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n131:T723,
// ./src/pages/_slices/nested/three.tsx\n\n// Create nested slice component\nexport default function SliceThree() {\n  return <p>🍰</p>;\n}\n\nexport const getConfig = () => {\n  return {\n    render: 'dynamic',\n  };\n};
7d:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$131\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n7e:[\"$\",\"h4\",null,{\"id\":\"using-slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#using-slices\",\"children\":\"Using slices\"}]}]\n7f:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Slices are used in pages and layouts by importing the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"Slice\"}],\" component from Waku and specifying the slice ID. The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"slices\"}],\" array in the page's \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"getConfig\"}],\" must include all slice IDs used on that page.\"]}]\n132:Tf4e,
// ./src/pages/some-page.tsx\nimport { Slice } from 'waku';\n\n// Create page with slices\nexport default function SomePage() {\n  return (\n    <div>\n      <Slice id=\"one\" />\n      <Slice id=\"two\" />\n      <Slice id=\"nested/three\" />\n    </div>\n  );\n}\n\nexport const getConfig = () => {\n  return {\n    render: 'static',\n    slices: ['one', 'two', 'nested/three'],\n  };\n};
80:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$132\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n81:[\"$\",\"h4\",null,{\"id\":\"lazy-slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#lazy-slices\",\"children\":\"Lazy slices\"}]}]\n82:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"Lazy 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.\"}]\n83:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Lazy slices are marked with the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"lazy\"}],\" prop and can include a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"fallback\"}],\" component to display while loading.\"]}]\n133:Tef2,
// ./src/pages/some-page.tsx\nimport { Slice } from 'waku';\n\n// Create page with lazy slice\nexport default function SomePage() {\n  return (\n    <div>\n      <Slice id=\"one\" />\n      <Slice id=\"two\" lazy fallback={<p>Two is loading...</p>} />\n    </div>\n  );\n}\n\nexport const getConfig = () => {\n  return {\n    render: 'static',\n    slices: ['one'], // Note: 'two' is lazy, so it is not included\n  };\n};
84:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$133\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n85:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"This allows you to have a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"dynamic\"}],\" slice component while keeping the rest of the page static.\"]}]\n134:T1dbf,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { RootLayout } from './templates/root-layout';\nimport { HomePage } from './templates/home-page';\nimport { Root } from './components/root';\nimport { Slice } from './components/slice';\n\nconst pages = createPages(\n  async ({ createPage, createLayout, createRoot, createSlice }) => [\n    // Create root component\n    // not required, but supported for customizing\n    // `<html>`, `<head>`, and `<body>` tags\n    createRoot({\n      render: 'static',\n      component: Root,\n    }),\n\n    // Create root layout\n    createLayout({\n      render: 'static',\n      path: '/',\n      component: RootLayout,\n    }),\n\n    // Create home page\n    createPage({\n      render: 'dynamic',\n      path: '/',\n      component: HomePage,\n    }),\n\n    // Create slice\n    createSlice({\n      render: 'static',\n      component: Slice,\n      id: 'slice-1',\n    }),\n  ],\n);\n\nexport default adapter(pages);
86:[[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The entry point for programmatic routing in Waku projects is \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.server.tsx\"}],\". In that file, call \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createPages(...)\"}],\" and pass the result to an adapter.\"]}],\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createLayout\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createPage\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createRoot\"}],\", and \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createSlice\"}],\" accept a configuration object to specify the route path, React component, and render method. Waku currently supports two options: \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"'static'\"}],\" for static prerendering (SSG) or \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"'dynamic'\"}],\" for server-side rendering (SSR).\"]}],\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"For 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\",[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$134\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}],\"\\n\",\"$L135\",\"\\n\",\"$L136\",\"\\n\",\"$L137\",\"\\n\",\"$L138\",\"\\n\",\"$L139\",\"\\n\",\"$L13a\",\"\\n\",\"$L13b\",\"\\n\",\"$L13c\",\"\\n\",\"$L13d\",\"\\n\",\"$L13e\",\"\\n\",\"$L13f\",\"\\n\",\"$L140\",\"\\n\",\"$L141\",\"\\n\",\"$L142\",\"\\n\",\"$L143\",\"\\n\",\"$L144\",\"\\n\",\"$L145\",\"\\n\",\"$L146\",\"\\n\",\"$L147\",\"\\n\",\"$L148\",\"\\n\",\"$L149\",\"\\n\",\"$L14a\",\"\\n\",\"$L14b\",\"\\n\",\"$L14c\",\"\\n\",\"$L14d\",\"\\n\",\"$L14e\",\"\\n\",\"$L14f\",\"\\n\",\"$L150\",\"\\n\",\"$L151\",\"\\n\",\"$L152\",\"\\n\",\"$L153\",\"\\n\",\"$L154\",\"\\n\",\"$L155\",\"\\n\",\"$L156\",\"\\n\",\"$L157\",\"\\n\",\"$L158\",\"\\n\",\"$L159\",\"\\n\",\"$L15a\",\"\\n\",\"$L15b\",\"\\n\",\"$L15c\",\"\\n\",\"$L15d\",\"\\n\",\"$L15e\",\"\\n\",\"$L15f\",\"\\n\",\"$L160\",\"\\n\",\"$L161\",\"\\n\",\"$L162\",\"\\n\",\"$L163\",\"\\n\",\"$L164\",\"\\n\",\"$L165\",\"\\n\",\"$L166\",\"\\n\",\"$L167\",\"\\n\",\"$L168\",\"\\n\",\"$L169\",\"\\n\",\"$L16a\",\"\\n\",\"$L16b\",\"\\n\",\"$L16c\",\"\\n\",\"$L16d\",\"\\n\",\"$L16e\",\"\\n\",\"$L16f\",\"\\n\",\"$L170\",\"\\n\",\"$L171\",\"\\n\",\"$L172\",\"\\n\",\"$L173\",\"\\n\",\"$L174\",\"\\n\",\"$L175\",\"\\n\",\"$L176\",\"\\n\",\"$L177\",\"\\n\",\"$L178\",\"\\n\",\"$L179\",\"\\n\",\"$L17a\",\"\\n\",\"$L17b\",\"\\n\",\"$L17c\",\"\\n\",\"$L17d\"]\n88:[\"$\",\"h3\",null,{\"id\":\"use-router\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#use-router\",\"children\":\"useRouter\"}]}]\n89:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"useRouter\"}],\" hook can be used to inspect the current route or perform programmatic navigation.\"]}]\n8a:[\"$\",\"h4\",null,{\"id\":\"router-properties\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#router-properties\",\"children\":\"router properties\"}]}]\n8b:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"router\"}],\" object has two properties related to the current route: \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"path\"}],\" (string) and \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"query\"}],\" (string).\"]}]\n17e:Ta60,
'use client';\n\nimport { useRouter } from 'waku';\n\nexport const Component = () => {\n  const { path, query } = useRouter();\n\n  return (\n    <>\n      <div>current path: {path}</div>\n      <div>current query: {query}</div>\n    </>\n  );\n};
8c:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$17e\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n8d:[\"$\",\"h4\",null,{\"id\":\"router-methods\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#router-methods\",\"children\":\"router methods\"}]}]\n8e:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"router\"}],\" object also contains several methods for programmatic navigation:\"]}]\n8f:[\"$\",\"ul\",null,{\"className\":\"mb-4 ml-4 list-disc text-base font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"router.push(to: string)\"}],\" - navigate to the provided route\"]}],\"\\n\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"router.prefetch(to: string)\"}],\" - prefetch the provided route\"]}],\"\\n\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"router.replace(to: string)\"}],\" - replace the current history entry\"]}],\"\\n\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"router.reload()\"}],\" - reload the current route\"]}],\"\\n\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"router.back()\"}],\" - navigate to the previous entry in the session history\"]}],\"\\n\"]}],\"\\n\",[\"$\",\"li\",null,{\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"router.forward()\"}],\" - navigate to the next entry in the session history\"]}],\"\\n\"]}],\"\\n\"]}]\n17f:Tc6a,
'use client';\n\nimport { useRouter } from 'waku';\n\nexport const Component = () => {\n  const router = useRouter();\n\n  return (\n    <>\n      <button onClick={() => router.push('/')}>Home</button>\n      <button onClick={() => router.back()}>Back</button>\n    </>\n  );\n};
90:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$17f\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n91:[\"$\",\"h2\",null,{\"id\":\"error-handling\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#error-handling\",\"children\":\"Error handling\"}]}]\n92:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Waku 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 \",[\"$\",\"a\",null,{\"href\":\"https://www.npmjs.com/package/react-error-boundary\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"react-error-boundary\"}]}],\" library.\"]}]\n93:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"When 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.\"}]\n180:T1597,
// ./src/pages/index.tsx\nimport { ErrorBoundary } from 'react-error-boundary';\n\nexport default async function HomePage() {\n  return (\n    <>\n      <ErrorBoundary fallback={<div>Caught server component error!</div>}>\n        <ThrowComponent />\n      </ErrorBoundary>\n      <ErrorBoundary fallback={<div>Caught server function error!</div>}>\n        <form\n          action={async () => {\n            'use server';\n            throw new Error('Oops!');\n          }}\n        >\n          <button>Crash</button>\n        </form>\n      </ErrorBoundary>\n    </>\n  );\n}\n\nconst ThrowComponent = async () => {\n  throw new Error('Oops!');\n  return <>...</>;\n};
94:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$180\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n95:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Error boundaries handle \",[\"$\",\"b\",null,{\"className\":\"font-bold text-white\",\"children\":\"unexpected errors\"}],\" as a last resort safety net. For expected error conditions (like validation or network failures), handle them explicitly in your application logic.\"]}]\n96:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"In 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.\"}]\n97:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"If you customize the root element (see \",[\"$\",\"a\",null,{\"href\":\"#root-element\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Root element\"}],\"), you should add your own error boundary there, as Waku's default root error boundary is included in the default root element.\"]}]\n98:[\"$\",\"h2\",null,{\"id\":\"metadata\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#metadata\",\"children\":\"Metadata\"}]}]\n99:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"Waku 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.\"}]\n181:Td38,
// ./src/pages/_layout.tsx\nexport default async function RootLayout({ children }) {\n  return (\n    <>\n      <link rel=\"icon\" type=\"image/png\" href=\"/images/favicon.png\" />\n      <meta property=\"og:image\" content=\"/images/opengraph.png\" />\n      {children}\n    </>\n  );\n}\n\nexport const getConfig = async () => {\n  return {\n    render: 'static',\n  } as const;\n};
9a:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$181\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n182:Td0f,
// ./src/pages/index.tsx\nexport default async function HomePage() {\n  return (\n    <>\n      <title>Waku</title>\n      <meta name=\"description\" content=\"The minimal React framework\" />\n      <h1>Waku</h1>\n      <div>Hello world!</div>\n    </>\n  );\n}\n\nexport const getConfig = async () => {\n  return {\n    render: 'static',\n  } as const;\n};
9b:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$182\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n9c:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"Metadata can also be generated programmatically.\"}]\n183:T135d,
// ./src/pages/index.tsx\nexport default async function HomePage() {\n  return (\n    <>\n      <Head />\n      <div>{/* ...*/}</div>\n    </>\n  );\n}\n\nconst Head = async () => {\n  const metadata = await getMetadata();\n\n  return (\n    <>\n      <title>{metadata.title}</title>\n      <meta name=\"description\" content={metadata.description} />\n    </>\n  );\n};\n\nconst getMetadata = async () => {\n  /* ... */\n};\n\nexport const getConfig = async () => {\n  return {\n    render: 'static',\n  } as const;\n};
9d:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$183\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n9e:[\"$\",\"h2\",null,{\"id\":\"styling\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#styling\",\"children\":\"Styling\"}]}]\n9f:[\"$\",\"h3\",null,{\"id\":\"global-styles\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#global-styles\",\"children\":\"Global styles\"}]}]\na0:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Install any required dev dependencies (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"npm i -D tailwindcss @tailwindcss/vite\"}],\") and set up any required configuration (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"waku.config.ts\"}],\"). Then create your global stylesheet (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/styles.css\"}],\") and import it into the root layout.\"]}]\n184:T86c,
// ./src/pages/_layout.tsx\nimport '../styles.css';\n\nexport default async function RootLayout({ children }) {\n  return <>{children}</>;\n}\n\nexport const getConfig = async () => {\n  return {\n    render: 'static',\n  } as const;\n};
a1:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$184\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\na2:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"
/* ./src/styles.css */\\n@import 'tailwindcss';
\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n185:T64a,
// ./waku.config.ts\nimport { defineConfig } from 'waku/config';\nimport tailwindcss from '@tailwindcss/vite';\n\nexport default defineConfig({\n  vite: {\n    plugins: [tailwindcss()],\n  },\n});
a3:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$185\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\na4:[\"$\",\"h2\",null,{\"id\":\"static-assets\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#static-assets\",\"children\":\"Static assets\"}]}]\na5:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Static assets such as images, fonts, stylesheets, and scripts can be placed in a special \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./public\"}],\" folder of the Waku project root directory. The public directory structure is served relative to the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"/\"}],\" base path.\"]}]\n186:T4fd,
// assuming image is saved at `/public/images/logo.svg`\n\nexport const Logo = () => {\n  return (\n    <>\n      <img src=\"/images/logo.svg\" />\n    </>\n  );\n};
a6:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$186\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\na7:[\"$\",\"h2\",null,{\"id\":\"file-system\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#file-system\",\"children\":\"File system\"}]}]\na8:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Files placed in a special \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./private\"}],\" folder of the Waku project root directory can be securely accessed in React server components.\"]}]\n187:T8fc,
export default async function HomePage() {\n  const file = readFileSync('./private/README.md', 'utf8');\n\n  return <>{/* ...*/}</>;\n}\n\nexport const getConfig = async () => {\n  return {\n    render: 'static',\n  } as const;\n};
a9:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$187\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\naa:[\"$\",\"h2\",null,{\"id\":\"data-fetching\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#data-fetching\",\"children\":\"Data fetching\"}]}]\nab:[\"$\",\"h3\",null,{\"id\":\"server\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#server\",\"children\":\"Server\"}]}]\nac:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"All 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.\"}]\n188:T1760,
// ./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<'/blog/[slug]'>) {\n  const article = await getArticle(slug);\n\n  return (\n    <>\n      <title>{article.frontmatter.title}</title>\n      <h1>{article.frontmatter.title}</h1>\n      <MDX>{article.content}</MDX>\n    </>\n  );\n}\n\nexport const getConfig = async () => {\n  const staticPaths = await getStaticPaths();\n\n  return {\n    render: 'static',\n    staticPaths,\n  } as const;\n};
ad:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$188\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nae:[\"$\",\"h3\",null,{\"id\":\"client\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#client\",\"children\":\"Client\"}]}]\naf:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"Data 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.\"}]\nb0:[\"$\",\"h2\",null,{\"id\":\"mutations\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#mutations\",\"children\":\"Mutations\"}]}]\nb1:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Data mutations can be performed via \",[\"$\",\"a\",null,{\"href\":\"https://react.dev/reference/rsc/server-actions\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"server actions\"}],\" or API endpoints.\"]}]\nb2:[\"$\",\"h3\",null,{\"id\":\"api-endpoints\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#api-endpoints\",\"children\":\"API endpoints\"}]}]\nb3:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Create API routes by making a new file in the special \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/pages/_api\"}],\" directory and exporting one or more functions named after the HTTP methods that you want it to support: \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"GET\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"HEAD\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"POST\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"PUT\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"DELETE\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"CONNECT\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"OPTIONS\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"TRACE\"}],\", or \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"PATCH\"}],\". The name of the file determines the route it will be served from. Each function receives a standard \",[\"$\",\"a\",null,{\"href\":\"https://developer.mozilla.org/en-US/docs/Web/API/Request\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Request\"}],\" object and returns a standard \",[\"$\",\"a\",null,{\"href\":\"https://developer.mozilla.org/en-US/docs/Web/API/Response\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Response\"}],\" object.\"]}]\n189:T1928,
// ./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<Response> => {\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: '[email protected]',\n      To: '[email protected]',\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};
b4:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$189\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nb5:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"Alternatively, you may export a default function as a \\\"catch-all\\\" handler that responds to all request methods.\"}]\n18a:T6d6,
// ./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}
b6:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$18a\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nb7:[\"$\",\"h4\",null,{\"id\":\"typed-route-parameters\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#typed-route-parameters\",\"children\":\"Typed route parameters\"}]}]\nb8:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"For API routes with dynamic segments (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/pages/_api/users/[id].ts\"}],\"), you can use \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"ApiContext\"}],\" as the second parameter to get typed access to route parameters. The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"params\"}],\" property will be automatically typed based on the path pattern.\"]}]\n18b:Ta56,
// ./src/pages/_api/users/[id].ts\nimport type { ApiContext } from 'waku/router';\n\nexport async function GET(\n  _req: Request,\n  { params }: ApiContext<'/users/[id]'>,\n) {\n  const { id } = params; // id is typed as string\n  return Response.json({ id, message: `Hello user ${id}` });\n}
b9:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$18b\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nba:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"This also works with multiple parameters and wildcard routes:\"}]\n18c:T960,
// ./src/pages/_api/files/[...path].ts\nimport type { ApiContext } from 'waku/router';\n\nexport async function GET(\n  _req: Request,\n  { params }: ApiContext<'/files/[...path]'>,\n) {\n  const { path } = params; // path is typed as string[]\n  return Response.json({ segments: path });\n}
bb:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$18c\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nbc:[\"$\",\"h4\",null,{\"id\":\"calling-api-routes\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#calling-api-routes\",\"children\":\"Calling API routes\"}]}]\nbd:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"API routes are accessible at paths with the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"_api\"}],\" prefix stripped. For example a file at \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/pages/_api/contact.ts\"}],\" is available at \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"/contact\"}],\", and \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/pages/_api/blog/rss.xml.ts\"}],\" is available at \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"/blog/rss.xml\"}],\". You can call these endpoints from your client components using the standard \",[\"$\",\"a\",null,{\"href\":\"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Fetch\"}],\" method.\"]}]\n18d:T3224,
'use client';\n\nimport { useState } from 'react';\n\nexport const ContactForm = () => {\n  const [message, setMessage] = useState('');\n  const [status, setStatus] = useState('idle');\n\n  const handleSubmit = async (event) => {\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    <form onSubmit={handleSubmit}>\n      <textarea\n        value={message}\n        onChange={(event) => setMessage(event.target.value)}\n        placeholder=\"Your message...\"\n        required\n      />\n      <button type=\"submit\" disabled={status === 'sending'}>\n        {status === 'sending' ? 'Sending...' : 'Send Message'}\n      </button>\n      {status === 'success' && <p>Message sent!</p>}\n      {status === 'error' && <p>Failed. Please try again.</p>}\n    </form>\n  );\n};
be:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$18d\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nbf:[\"$\",\"h4\",null,{\"id\":\"configuring-api-routes\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#configuring-api-routes\",\"children\":\"Configuring API routes\"}]}]\nc0:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"API 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 \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"getConfig\"}],\" function that returns a config object with the render property set to \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"'static'\"}],\".\"]}]\n18e:Tad1,
// ./src/pages/_api/blog/rss.xml.ts\n\nexport const GET = async () => {\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 () => {\n  return {\n    render: 'static',\n  } as const;\n};
c1:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$18e\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nc2:[\"$\",\"h3\",null,{\"id\":\"server-actions\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#server-actions\",\"children\":\"Server actions\"}]}]\nc3:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Server 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 \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"POST\"}],\" requests to them with \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"fetch\"}],\", or managing pending states and errors.\"]}]\nc4:[\"$\",\"h4\",null,{\"id\":\"defining-and-protecting-actions\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#defining-and-protecting-actions\",\"children\":\"Defining and protecting actions\"}]}]\nc5:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"'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.\"]}]\nc6:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"When 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 \",[\"$\",\"i\",null,{\"className\":\"italic text-white/70\",\"children\":\"all\"}],\" exported functions as actions at once.\"]}]\nc7:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Be careful not to add the directive where inappropriate and inadvertently create unwanted endpoints. Endpoints created by server actions are \",[\"$\",\"i\",null,{\"className\":\"italic text-white/70\",\"children\":\"not\"}],\" secured unless you add your own authentication and authorization logic inside the function body.\"]}]\nc8:[\"$\",\"div\",null,{\"className\":\"sm:-mx-3! mb-6 overflow-clip rounded-xl bg-gray-950\",\"children\":[\"$\",\"blockquote\",null,{\"className\":\"blockquote group p-4 sm:p-6\",\"children\":[[\"$\",\"div\",null,{\"className\":\"mb-1 flex items-center gap-2\",\"children\":[[\"$\",\"svg\",null,{\"xmlns\":\"http://www.w3.org/2000/svg\",\"viewBox\":\"0 0 128 128\",\"width\":\"64px\",\"height\":\"64px\",\"className\":\"size-3.5 fill-current text-white\",\"children\":[\"$\",\"path\",null,{\"d\":\"M 64 6 C 32 6 6 32 6 64 C 6 96 32 122 64 122 C 96 122 122 96 122 64 C 122 32 96 6 64 6 z M 64 12 C 92.7 12 116 35.3 116 64 C 116 92.7 92.7 116 64 116 C 35.3 116 12 92.7 12 64 C 12 35.3 35.3 12 64 12 z M 64 30 A 9 9 0 0 0 64 48 A 9 9 0 0 0 64 30 z M 64 59 C 59 59 55 63 55 68 L 55 92 C 55 97 59 101 64 101 C 69 101 73 97 73 92 L 73 68 C 73 63 69 59 64 59 z\"}]}],[\"$\",\"div\",null,{\"className\":\"font-badge text-sm uppercase leading-none text-white sm:text-base\",\"children\":\"Note\"}]]}],[\"$\",\"div\",null,{\"className\":\"*:text-sm! *:text-white/60! last:*:mb-0! sm:*:text-base!\",\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"'use server'\"}],\" directive has no relation to the\",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"'use client'\"}],\" directive. It does \",[\"$\",\"b\",null,{\"className\":\"font-bold text-white\",\"children\":\"not\"}],\" mark a component as a server component and should \",[\"$\",\"b\",null,{\"className\":\"font-bold text-white\",\"children\":\"not\"}],\" be placed at the top of server components!\"]}],\"\\n\"]}]]}]}]\nc9:[\"$\",\"h4\",null,{\"id\":\"making-and-consuming-actions\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#making-and-consuming-actions\",\"children\":\"Making and consuming actions\"}]}]\nca:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"When creating an inline server action within a server component, it can be passed as props to a client component.\"}]\n18f:T986,
// ./src/pages/contact.tsx\n\nimport db from 'some-db';\n\nexport default async function ContactPage() {\n  const sendMessage = async (message: string) => {\n    'use server';\n    await db.messages.create(message);\n  };\n\n  return <ContactForm sendMessage={sendMessage} />;\n}
cb:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$18f\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n190:Tfd1,
// ./src/components/contact-form.tsx\n'use client';\n\nimport { useState } from 'react';\n\nexport const ContactForm = ({ sendMessage }) => {\n  const [message, setMessage] = useState('');\n\n  return (\n    <>\n      <textarea\n        value={message}\n        onChange={(event) => setMessage(event.target.value)}\n        rows={4}\n      />\n      <button onClick={() => sendMessage(message)}>Send message</button>\n    </>\n  );\n};
cc:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$190\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\ncd:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"When creating server actions in a separate file, they can be imported directly into client components.\"}]\nce:[\"$\",\"div\",null,{\"className\":\"sm:-mx-3! mb-6 overflow-clip rounded-xl bg-gray-950\",\"children\":[\"$\",\"blockquote\",null,{\"className\":\"blockquote group p-4 sm:p-6\",\"children\":[[\"$\",\"div\",null,{\"className\":\"mb-1 flex items-center gap-2\",\"children\":[[\"$\",\"svg\",null,{\"xmlns\":\"http://www.w3.org/2000/svg\",\"viewBox\":\"0 0 128 128\",\"width\":\"64px\",\"height\":\"64px\",\"className\":\"size-3.5 fill-current text-white\",\"children\":[\"$\",\"path\",null,{\"d\":\"M 64 6 C 32 6 6 32 6 64 C 6 96 32 122 64 122 C 96 122 122 96 122 64 C 122 32 96 6 64 6 z M 64 12 C 92.7 12 116 35.3 116 64 C 116 92.7 92.7 116 64 116 C 35.3 116 12 92.7 12 64 C 12 35.3 35.3 12 64 12 z M 64 30 A 9 9 0 0 0 64 48 A 9 9 0 0 0 64 30 z M 64 59 C 59 59 55 63 55 68 L 55 92 C 55 97 59 101 64 101 C 69 101 73 97 73 92 L 73 68 C 73 63 69 59 64 59 z\"}]}],[\"$\",\"div\",null,{\"className\":\"font-badge text-sm uppercase leading-none text-white sm:text-base\",\"children\":\"Note\"}]]}],[\"$\",\"div\",null,{\"className\":\"*:text-sm! *:text-white/60! last:*:mb-0! sm:*:text-base!\",\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"When using a top-level \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"'use server'\"}],\" directive, note that \",[\"$\",\"i\",null,{\"className\":\"italic text-white/70\",\"children\":\"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\"]}]]}]}]\n191:T63d,
// ./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}
cf:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$191\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n192:T8e7,
// ./src/components/contact-button.tsx\n'use client';\n\nimport { sendMessage } from '../actions/send-message';\n\nexport const ContactButton = () => {\n  const message = `Hello world!`;\n\n  return <button onClick={() => sendMessage(message)}>Send message</button>;\n};
d0:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$192\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nd1:[\"$\",\"h4\",null,{\"id\":\"invoking-actions\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#invoking-actions\",\"children\":\"Invoking actions\"}]}]\nd2:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Actions can be invoked via event handlers such as \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"onClick\"}],\" or \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"onSubmit\"}],\", as in the examples above, or in a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"useEffect\"}],\" hook, based on whichever conditions you choose.\"]}]\nd3:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"They can also be invoked via an \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"action\"}],\" prop on native \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"
\"}],\" elements. In this case the server action will automatically receive a parameter of \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"FormData\"}],\" with all of the form field values, including hidden ones.\"]}]\n193:T821,
// ./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}
d4:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$193\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n194:Te34,
// ./src/components/create-todo-button.tsx\n'use client';\n\nimport { sendMessage } from '../actions/send-message';\n\nexport const ContactForm = () => {\n  return (\n    <form action={sendMessage}>\n      <textarea name=\"message\" rows={4} />\n      <input type=\"hidden\" name=\"secret-message\" value=\"This too!\" />\n      <button type=\"submit\">Send message</button>\n    </form>\n  );\n};
d5:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$194\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nd6:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"If you must pass additional arguments to a form action beyond its native form fields, you can use the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"bind\"}],\" method to create an extended server action with the extra arguments.\"]}]\n195:Te3f,
// ./src/components/create-todo-button.tsx\n'use client';\n\nimport { sendMessage } from '../actions/send-message';\n\nexport const ContactForm = ({ author = 'guest' }) => {\n  const sendMessageWithAuthor = sendMessage.bind(null, author);\n\n  return (\n    <form action={sendMessageWithAuthor}>\n      <textarea name=\"message\" rows={4} />\n      <button type=\"submit\">Send message</button>\n    </form>\n  );\n};
d7:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$195\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nd8:[\"$\",\"h4\",null,{\"id\":\"enhancing-actions\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#enhancing-actions\",\"children\":\"Enhancing actions\"}]}]\nd9:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Server actions integrate with many other React APIs such as the \",[\"$\",\"a\",null,{\"href\":\"https://react.dev/reference/react/useTransition\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"useTransition\"}]}],\" hook for handling pending states, the \",[\"$\",\"a\",null,{\"href\":\"https://react.dev/reference/react/useActionState\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"useActionState\"}]}],\" hook for accessing returned values, and the \",[\"$\",\"a\",null,{\"href\":\"https://react.dev/reference/react/useOptimistic\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"useOptimistic\"}]}],\" hook for performing optimistic UI updates.\"]}]\nda:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"See the talk \",[\"$\",\"a\",null,{\"href\":\"https://www.youtube.com/watch?v=AJOGzVygGcY\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"What’s new in React 19?\"}],\" to learn more.\"]}]\ndb:[\"$\",\"h2\",null,{\"id\":\"state-management\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#state-management\",\"children\":\"State management\"}]}]\ndc:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"We recommend \",[\"$\",\"a\",null,{\"href\":\"https://jotai.org\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Jotai\"}],\" 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.\"]}]\ndd:[\"$\",\"div\",null,{\"className\":\"sm:-mx-3! mb-6 overflow-clip rounded-xl bg-gray-950\",\"children\":[\"$\",\"blockquote\",null,{\"className\":\"blockquote group p-4 sm:p-6\",\"children\":[[\"$\",\"div\",null,{\"className\":\"mb-1 flex items-center gap-2\",\"children\":[[\"$\",\"svg\",null,{\"xmlns\":\"http://www.w3.org/2000/svg\",\"viewBox\":\"0 0 128 128\",\"width\":\"64px\",\"height\":\"64px\",\"className\":\"size-3.5 fill-current text-white\",\"children\":[\"$\",\"path\",null,{\"d\":\"M 64 6 C 32 6 6 32 6 64 C 6 96 32 122 64 122 C 96 122 122 96 122 64 C 122 32 96 6 64 6 z M 64 12 C 92.7 12 116 35.3 116 64 C 116 92.7 92.7 116 64 116 C 35.3 116 12 92.7 12 64 C 12 35.3 35.3 12 64 12 z M 64 30 A 9 9 0 0 0 64 48 A 9 9 0 0 0 64 30 z M 64 59 C 59 59 55 63 55 68 L 55 92 C 55 97 59 101 64 101 C 69 101 73 97 73 92 L 73 68 C 73 63 69 59 64 59 z\"}]}],[\"$\",\"div\",null,{\"className\":\"font-badge text-sm uppercase leading-none text-white sm:text-base\",\"children\":\"Note\"}]]}],[\"$\",\"div\",null,{\"className\":\"*:text-sm! *:text-white/60! last:*:mb-0! sm:*:text-base!\",\"children\":[\"\\n\",[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"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\"]}]]}]}]\nde:[\"$\",\"h2\",null,{\"id\":\"environment-variables\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#environment-variables\",\"children\":\"Environment variables\"}]}]\ndf:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"It’s important to distinguish environment variables that must be kept secret from those that can be made public.\"}]\ne0:[\"$\",\"h4\",null,{\"id\":\"private\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#private\",\"children\":\"Private\"}]}]\ne1:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"By 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.\"}]\ne2:[\"$\",\"h4\",null,{\"id\":\"public\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#public\",\"children\":\"Public\"}]}]\ne3:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"A special \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"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.\"]}]\ne4:[\"$\",\"h3\",null,{\"id\":\"runtime-agnostic-recommended\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#runtime-agnostic-recommended\",\"children\":\"Runtime agnostic (recommended)\"}]}]\ne5:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Environment variables are available on the server via the Waku \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"getEnv\"}],\" function and on the client via \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"import.meta.env\"}],\".\"]}]\n196:T6bd,
// server components can access both private and public variables\nimport { getEnv } from 'waku';\n\nexport const ServerComponent = async () => {\n  const secretKey = getEnv('SECRET_KEY');\n\n  return <>{/* ...*/}</>;\n};
e6:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$196\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n197:T621,
// client components can only access public variables\n'use client';\n\nexport const ClientComponent = () => {\n  const publicStatement = import.meta.env.WAKU_PUBLIC_HELLO;\n\n  return <>{/* ...*/}</>;\n};
e7:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$197\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\ne8:[\"$\",\"h3\",null,{\"id\":\"node-js\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#node-js\",\"children\":\"Node.js\"}]}]\ne9:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"In Node.js environments, \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"process.env\"}],\" may also be used.\"]}]\nea:[\"$\",\"h2\",null,{\"id\":\"deployment\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#deployment\",\"children\":\"Deployment\"}]}]\neb:[\"$\",\"h3\",null,{\"id\":\"node-js-default\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#node-js-default\",\"children\":\"Node.js (default)\"}]}]\nec:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"After building, \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"waku start\"}],\" runs the production server.\"]}]\ned:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"If you need a standalone app (for example, for Docker),\\nthe build output lives in \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"dist\"}],\". It is the only folder you need to copy, then run \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"node dist/serve-node.js\"}],\".\"]}]\nee:[\"$\",\"h3\",null,{\"id\":\"pure-ssg\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#pure-ssg\",\"children\":\"Pure SSG\"}]}]\nef:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The build output for SSG lives in \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"dist/public\"}],\". You can copy (or upload to any hosting service) the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"dist/public\"}],\" folder.\\nWith Pure SSG, dynamic features (like dynamic rendering, server actions, API routes) do not work.\"]}]\nf0:[\"$\",\"h3\",null,{\"id\":\"vercel\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#vercel\",\"children\":\"Vercel\"}]}]\nf1:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Waku projects can be deployed to Vercel with the \",[\"$\",\"a\",null,{\"href\":\"https://vercel.com/docs/cli\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Vercel CLI\"}],\" automatically.\"]}]\nf2:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"
vercel
\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nf3:[\"$\",\"h4\",null,{\"id\":\"pure-ssg-with-vercel\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#pure-ssg-with-vercel\",\"children\":\"Pure SSG with Vercel\"}]}]\nf4:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"For advanced users who want to avoid deploying functions, use the server entry file with the Vercel adapter and specify the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"static\"}],\" option.\"]}]\nf5:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.server.tsx\"}],\":\"]}]\n198:T806,
import { 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);
f6:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$198\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nf7:[\"$\",\"h3\",null,{\"id\":\"netlify\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#netlify\",\"children\":\"Netlify\"}]}]\nf8:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Waku projects can be deployed to Netlify with the \",[\"$\",\"a\",null,{\"href\":\"https://docs.netlify.com/cli/get-started/\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Netlify CLI\"}],\".\"]}]\nf9:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"
NETLIFY=1 npm run build\\nnetlify deploy
\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nfa:[\"$\",\"h4\",null,{\"id\":\"pure-ssg-with-netlify\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#pure-ssg-with-netlify\",\"children\":\"Pure SSG with Netlify\"}]}]\nfb:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"For advanced users who want to avoid deploying functions, use the server entry file with the Netlify adapter and specify the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"static\"}],\" option.\"]}]\nfc:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.server.tsx\"}],\":\"]}]\n199:T807,
import { 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);
fd:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$199\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\nfe:[\"$\",\"h3\",null,{\"id\":\"cloudflare-workers\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#cloudflare-workers\",\"children\":\"Cloudflare Workers\"}]}]\nff:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Waku projects can be deployed to Cloudflare Workers with \",[\"$\",\"a\",null,{\"href\":\"https://developers.cloudflare.com/workers/wrangler/\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Wrangler\"}],\".\"]}]\n100:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"
CLOUDFLARE=1 npm run build\\nwrangler deploy
\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n101:[\"$\",\"h4\",null,{\"id\":\"pure-ssg-with-cloudflare-workers\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#pure-ssg-with-cloudflare-workers\",\"children\":\"Pure SSG with Cloudflare Workers\"}]}]\n102:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.server.tsx\"}],\":\"]}]\n19a:T80a,
import { 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);
103:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$19a\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n104:[\"$\",\"h3\",null,{\"id\":\"deno-deploy-experimental\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#deno-deploy-experimental\",\"children\":\"Deno Deploy (experimental)\"}]}]\n105:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.server.tsx\"}],\":\"]}]\n19b:T727,
import { fsRouter } from 'waku';\nimport adapter from 'waku/adapters/deno';\n\nexport default adapter(\n  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),\n);
106:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$19b\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n107:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"
npm run build\\ndeployctl deploy --prod dist/serve-deno.js --exclude node_modules
\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n108:[\"$\",\"h3\",null,{\"id\":\"bun-experimental\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#bun-experimental\",\"children\":\"Bun (experimental)\"}]}]\n109:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.server.tsx\"}],\":\"]}]\n19c:T726,
import { fsRouter } from 'waku';\nimport adapter from 'waku/adapters/bun';\n\nexport default adapter(\n  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),\n);
10a:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$19c\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n10b:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"
npm run build
\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n10c:[\"$\",\"h3\",null,{\"id\":\"aws-lambda-experimental\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#aws-lambda-experimental\",\"children\":\"AWS Lambda (experimental)\"}]}]\n10d:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.server.tsx\"}],\":\"]}]\n19d:T805,
import { 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);
10e:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$19d\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n10f:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"
npm run build
\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n110:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The handler entrypoint is \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"dist/serve-aws-lambda.js\"}],\": see \",[\"$\",\"a\",null,{\"href\":\"https://hono.dev/getting-started/aws-lambda#_3-deploy\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Hono AWS Lambda Deploy Docs\"}],\".\"]}]\n111:[\"$\",\"h3\",null,{\"id\":\"edge\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#edge\",\"children\":\"Edge\"}]}]\n112:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"waku/adapters/edge\"}],\" adapter provides a minimal server output without deployment target specific code. For example, you can use it with \",[\"$\",\"a\",null,{\"href\":\"https://nitro.build/\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Nitro\"}],\" to handle packaging for various deployment platforms. See \",[\"$\",\"a\",null,{\"href\":\"https://github.com/hi-ogawa/waku-nitro-example\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"waku-nitro-example\"}],\" for the example.\"]}]\n19e:T46e,
// [waku.config.ts]\nimport { defineConfig } from 'waku/config';\n\nexport default defineConfig({\n  unstable_adapter: 'waku/adapters/edge',\n});
113:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$19e\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n114:[\"$\",\"h2\",null,{\"id\":\"community\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#community\",\"children\":\"Community\"}]}]\n115:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Please join our friendly \",[\"$\",\"a\",null,{\"href\":\"https://github.com/wakujs/waku/discussions\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"GitHub discussions\"}],\" or \",[\"$\",\"a\",null,{\"href\":\"https://discord.gg/MrQdmzd\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Discord server\"}],\" to participate in the Waku community. Hope to see you there!\"]}]\n116:[\"$\",\"h2\",null,{\"id\":\"roadmap\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#roadmap\",\"children\":\"Roadmap\"}]}]\n117:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Waku is in active development and we’re seeking additional contributors. Check out our \",[\"$\",\"a\",null,{\"href\":\"https://github.com/wakujs/waku/issues/24\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"roadmap\"}],\" for more information.\"]}]\n118:[\"$\",\"h2\",null,{\"id\":\"contributing\",\"className\":\"mb-2 mt-16 scroll-mt-16 text-balance text-3xl font-bold leading-none text-white first:mt-0 sm:text-[2.75rem] xl:mt-20 xl:scroll-mt-20\",\"children\":[\"$\",\"a\",null,{\"href\":\"#contributing\",\"children\":\"Contributing\"}]}]\n119:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"If you would like to contribute, please see \",[\"$\",\"a\",null,{\"href\":\"https://github.com/wakujs/waku/blob/main/CONTRIBUTING.md\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"CONTRIBUTING.md\"}],\"!\"]}]\n135:[\"$\",\"h3\",null,{\"id\":\"pages\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#pages\",\"children\":\"Pages\"}]}]\n136:[\"$\",\"h4\",null,{\"id\":\"single-routes\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#single-routes\",\"children\":\"Single routes\"}]}]\n137:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Pages can be rendered as a single route (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"/about\"}],\").\"]}]\n19f:T11ad,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { AboutPage } from './templates/about-page';\nimport { BlogIndexPage } from './templates/blog-index-page';\n\nconst pages = createPages(async ({ createPage }) => [\n  // Create about page\n  createPage({\n    render: 'static',\n    path: '/about',\n    component: AboutPage,\n  }),\n\n  // Create blog index page\n  createPage({\n    render: 'static',\n    path: '/blog',\n    component: BlogIndexPage,\n  }),\n]);\n\nexport default adapter(pages);
138:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$19f\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n139:[\"$\",\"h4\",null,{\"id\":\"segment-routes\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#segment-routes\",\"children\":\"Segment routes\"}]}]\n13a:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Pages can also render a segment route (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"/blog/[slug]\"}],\"). The rendered React component automatically receives a prop named by the segment (e.g, \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"slug\"}],\") with the value of the rendered segment (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"'introducing-waku'\"}],\"). If statically prerendering a segment route at build time, a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"staticPaths\"}],\" array must also be provided.\"]}]\n13b:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[[\"$\",\"b\",null,{\"className\":\"font-bold text-white\",\"children\":\"Note:\"}],\" When using \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"staticPaths\"}],\", spaces in slug values are sanitized to \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"-\"}],\".\"]}]\n1a0:T14b9,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { BlogArticlePage } from './templates/blog-article-page';\nimport { ProductCategoryPage } from './templates/product-category-page';\n\nconst pages = createPages(async ({ createPage }) => [\n  // Create blog article pages\n  // `<BlogArticlePage>` receives `slug` prop\n  createPage({\n    render: 'static',\n    path: '/blog/[slug]',\n    staticPaths: ['introducing-waku', 'introducing-create-pages'],\n    component: BlogArticlePage,\n  }),\n\n  // Create product category pages\n  // `<ProductCategoryPage>` receives `category` prop\n  createPage({\n    render: 'dynamic',\n    path: '/shop/[category]',\n    component: ProductCategoryPage,\n  }),\n]);\n\nexport default adapter(pages);
13c:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a0\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n13d:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"Static paths (or other values) could also be generated programmatically.\"}]\n1a1:T102b,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { getBlogPaths } from './lib/get-blog-paths';\nimport { BlogArticlePage } from './templates/blog-article-page';\n\nconst pages = createPages(async ({ createPage }) => {\n  const blogPaths = await getBlogPaths();\n\n  return [\n    createPage({\n      render: 'static',\n      path: '/blog/[slug]',\n      staticPaths: blogPaths,\n      component: BlogArticlePage,\n    }),\n  ];\n});\n\nexport default adapter(pages);
13e:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a1\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n13f:[\"$\",\"h4\",null,{\"id\":\"nested-segment-routes\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#nested-segment-routes\",\"children\":\"Nested segment routes\"}]}]\n140:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Routes can contain multiple segments (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"/shop/[category]/[product]\"}],\").\"]}]\n1a2:Tced,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { ProductDetailPage } from './templates/product-detail-page';\n\nconst pages = createPages(async ({ createPage }) => [\n  // Create product detail pages\n  // `<ProductDetailPage>` receives `category` and `product` props\n  createPage({\n    render: 'dynamic',\n    path: '/shop/[category]/[product]',\n    component: ProductDetailPage,\n  }),\n]);\n\nexport default adapter(pages);
141:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a2\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n142:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"For static prerendering of nested segment routes, the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"staticPaths\"}],\" array is instead composed of ordered arrays.\"]}]\n1a3:T10c5,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { ProductDetailPage } from './templates/product-detail-page';\n\nconst pages = createPages(async ({ createPage }) => [\n  // Create product detail pages\n  // `<ProductDetailPage>` receives `category` and `product` props\n  createPage({\n    render: 'static',\n    path: '/shop/[category]/[product]',\n    staticPaths: [\n      ['some-category', 'some-product'],\n      ['some-category', 'another-product'],\n    ],\n    component: ProductDetailPage,\n  }),\n]);\n\nexport default adapter(pages);
143:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a3\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n144:[\"$\",\"h4\",null,{\"id\":\"catch-all-routes\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#catch-all-routes\",\"children\":\"Catch-all routes\"}]}]\n145:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Catch-all or \\\"wildcard\\\" routes (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"/app/[...catchAll]\"}],\") have indefinite segments. Wildcard routes receive a prop with segment values as an ordered array.\"]}]\n146:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"For example, the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"/app/profile/settings\"}],\" route would receive a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"catchAll\"}],\" prop with the value \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"['profile', 'settings']\"}],\". These values can then be used to determine what to render in the component.\"]}]\n1a4:Tccd,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { DashboardPage } from './templates/dashboard-page';\n\nconst pages = createPages(async ({ createPage }) => [\n  // Create account dashboard\n  // `<DashboardPage>` receives `catchAll` prop (string[])\n  createPage({\n    render: 'dynamic',\n    path: '/app/[...catchAll]',\n    component: DashboardPage,\n  }),\n]);\n\nexport default adapter(pages);
147:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a4\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n148:[\"$\",\"h3\",null,{\"id\":\"router-paths-type-safety\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#router-paths-type-safety\",\"children\":\"Router paths type safety\"}]}]\n149:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Waku provides inference for the router paths when created pages are returned from the callback passed into \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createPages\"}],\". The following example shows how to setup router paths type safety.\"]}]\n1a5:T1830,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\nimport type { PathsForPages } from 'waku/router';\n\nimport { RootLayout } from './templates/root-layout';\nimport { HomePage } from './templates/home-page';\n\nconst pages = createPages(async ({ createPage, createLayout }) => [\n  createLayout({\n    render: 'static',\n    path: '/',\n    component: RootLayout,\n  }),\n\n  createPage({\n    render: 'dynamic',\n    path: '/',\n    component: HomePage,\n  }),\n]);\n\ndeclare module 'waku/router' {\n  interface RouteConfig {\n    paths: PathsForPages<typeof pages>;\n  }\n  interface CreatePagesConfig {\n    pages: typeof pages;\n  }\n}\n\nexport default adapter(pages);
14a:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a5\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n14b:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Once this is done, any \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}],\" component or hook from \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"waku/router\"}],\" that uses paths in your app will use this type. In this case, the one valid use would be \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}],\", but as you add more pages to the router, this type will grow to include them.\"]}]\n14c:[\"$\",\"h4\",null,{\"id\":\"page-props\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#page-props\",\"children\":\"PageProps\"}]}]\n14d:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"PageProps\"}],\" type gives you type safety for the path and slug parameters in your pages.\"]}]\n1a6:T712,
// ./src/templates/about-page.tsx\nimport type { PageProps } from 'waku/router';\n\n// PageProps<'/about/[foo]'> => { path: `/about/${string}`; foo: string; query: string; hash: string; }\nexport const AboutPage = ({ foo }: PageProps<'/about/[foo]'>) => {\n  return <>{/* ...*/}</>;\n};
14e:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a6\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n14f:[\"$\",\"h3\",null,{\"id\":\"layouts\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#layouts\",\"children\":\"Layouts\"}]}]\n150:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Layouts wrap an entire route and its descendents. They must accept a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"children\"}],\" prop of type \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"ReactNode\"}],\". While not required, you will typically want at least a root layout.\"]}]\n151:[\"$\",\"h4\",null,{\"id\":\"root-layout\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#root-layout\",\"children\":\"Root layout\"}]}]\n152:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The root layout rendered at \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"path: '/'\"}],\" 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.\"]}]\n1a7:Tc3f,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { RootLayout } from './templates/root-layout';\n\nconst pages = createPages(async ({ createLayout }) => [\n  // Add a global header and footer\n  createLayout({\n    render: 'static',\n    path: '/',\n    component: RootLayout,\n  }),\n]);\n\nexport default adapter(pages);
153:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a7\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n1a8:T11ec,
// ./src/templates/root-layout.tsx\nimport '../styles.css';\n\nimport { Providers } from '../components/providers';\nimport { Header } from '../components/header';\nimport { Footer } from '../components/footer';\n\nexport const RootLayout = async ({ children }) => {\n  return (\n    <Providers>\n      <link rel=\"icon\" type=\"image/png\" href=\"/images/favicon.png\" />\n      <meta property=\"og:image\" content=\"/images/opengraph.png\" />\n      <Header />\n      <main>{children}</main>\n      <Footer />\n    </Providers>\n  );\n};
154:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a8\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n1a9:T8a5,
// ./src/components/providers.tsx\n'use client';\n\nimport { createStore, Provider } from 'jotai';\n\nconst store = createStore();\n\nexport const Providers = ({ children }) => {\n  return <Provider store={store}>{children}</Provider>;\n};
155:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1a9\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n156:[\"$\",\"h4\",null,{\"id\":\"other-layouts\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#other-layouts\",\"children\":\"Other layouts\"}]}]\n157:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Layouts are also helpful further down the tree. For example, you could add a layout at \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"path: '/blog'\"}],\" to add a sidebar to both the blog index and all blog article pages.\"]}]\n1aa:Tc5b,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { BlogLayout } from './templates/blog-layout';\n\nconst pages = createPages(async ({ createLayout }) => [\n  // Add a sidebar to the blog index and blog article pages\n  createLayout({\n    render: 'static',\n    path: '/blog',\n    component: BlogLayout,\n  }),\n]);\n\nexport default adapter(pages);
158:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1aa\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n1ab:T8fa,
// ./src/templates/blog-layout.tsx\nimport { Sidebar } from '../components/sidebar';\n\nexport const BlogLayout = async ({ children }) => {\n  return (\n    <div className=\"flex\">\n      <div>{children}</div>\n      <Sidebar />\n    </div>\n  );\n};
159:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1ab\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n15a:[\"$\",\"h3\",null,{\"id\":\"root-component\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#root-component\",\"children\":\"Root component\"}]}]\n15b:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Root component is a special component that is rendered at the root of html document. It is useful for customizing \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}],\", \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}],\", and \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}],\" tags.\"]}]\n1ac:T789,
// ./src/components/root.tsx\n\nexport const Root = ({ children }) => {\n  return (\n    <html lang=\"en\">\n      <head></head>\n      <body>{children}</body>\n    </html>\n  );\n};
15c:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1ac\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n15d:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Add this with \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createRoot\"}],\" function inside \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createPages\"}],\".\"]}]\n15e:[\"$\",\"h4\",null,{\"id\":\"\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#\",\"children\":[\"Note About \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}]]}]}]\n15f:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"If you only need to customize \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}],\", you can rely on React's \",[\"$\",\"a\",null,{\"href\":\"https://react.dev/blog/2024/04/25/react-19#support-for-metadata-tags\",\"className\":\"text-white/80 underline decoration-white/60 decoration-1 transition-colors duration-300 ease-in-out hover:text-white\",\"target\":\"_blank\",\"rel\":\"noreferrer\",\"children\":\"Support for Document Metadata\"}],\" and do not need to use \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createRoot\"}],\".\"]}]\n160:[\"$\",\"h3\",null,{\"id\":\"client-entry-point\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#client-entry-point\",\"children\":\"Client entry point\"}]}]\n161:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"The file \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.server.tsx\"}],\" is the entry point for the server.\\nFor the client, the entry point file is \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.client.tsx\"}],\".\"]}]\n162:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"The default client entry file content is the following.\"}]\n1ad:Tcee,
import { Component, StrictMode } from 'react';\nimport { createRoot, hydrateRoot } from 'react-dom/client';\nimport { Router } from 'waku/router/client';\n\nconst rootElement = (\n  <StrictMode>\n    <Router />\n  </StrictMode>\n);\n\nif (globalThis.__WAKU_HYDRATE__) {\n  hydrateRoot(document, rootElement);\n} else {\n  createRoot(document).render(rootElement);\n}
163:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1ad\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n164:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"You can omit \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"./src/waku.client.tsx\"}],\" unless you need to modify it.\"]}]\n165:[\"$\",\"h3\",null,{\"id\":\"slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-xl font-bold leading-none text-white sm:text-3xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#slices\",\"children\":\"Slices\"}]}]\n166:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Slices are reusable components that can be rendered \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"static\"}],\" or \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"dynamic\"}],\" in their own right. This allows for patterns like adding \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"static\"}],\" components wherever they'll compose nicely inside your \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"dynamic\"}],\" or \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"static\"}],\" layouts & pages.\"]}]\n167:[\"$\",\"h4\",null,{\"id\":\"creating-slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#creating-slices\",\"children\":\"Creating slices\"}]}]\n168:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Slices are created with \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createSlice\"}],\" inside \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"createPages\"}],\". Each slice needs a unique \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"id\"}],\", a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"component\"}],\", and a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"render\"}],\" mode.\"]}]\n1ae:T1197,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { SliceOne } from './components/slice-one';\nimport { SliceTwo } from './components/slice-two';\n\nconst pages = createPages(async ({ createSlice }) => [\n  // Create static slice\n  createSlice({\n    render: 'static',\n    component: SliceOne,\n    id: 'one',\n  }),\n\n  // Create dynamic slice\n  createSlice({\n    render: 'dynamic',\n    component: SliceTwo,\n    id: 'two',\n  }),\n]);\n\nexport default adapter(pages);
169:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1ae\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n16a:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"
// ./src/components/slice-one.tsx\\n\\nexport const SliceOne = () => {\\n  return <p>Static slice content</p>;\\n};
\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n16b:[\"$\",\"h4\",null,{\"id\":\"using-slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#using-slices\",\"children\":\"Using slices\"}]}]\n16c:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Slices are used in pages and layouts by importing the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"Slice\"}],\" component from Waku and specifying the slice ID. The \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"slices\"}],\" array in the page's config must include all slice IDs used on that page.\"]}]\n1af:T833,
// ./src/templates/home-page.tsx\nimport { Slice } from 'waku';\n\nexport const HomePage = () => {\n  return (\n    <div>\n      <Slice id=\"one\" />\n      <Slice id=\"two\" />\n    </div>\n  );\n};
16d:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1af\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n1b0:T1803,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { HomePage } from './templates/home-page';\nimport { SliceOne } from './components/slice-one';\nimport { SliceTwo } from './components/slice-two';\n\nconst pages = createPages(async ({ createPage, createSlice }) => [\n  createPage({\n    render: 'static',\n    path: '/',\n    component: HomePage,\n    slices: ['one', 'two'],\n  }),\n\n  createSlice({\n    render: 'static',\n    component: SliceOne,\n    id: 'one',\n  }),\n\n  createSlice({\n    render: 'dynamic',\n    component: SliceTwo,\n    id: 'two',\n  }),\n]);\n\nexport default adapter(pages);
16e:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1b0\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n16f:[\"$\",\"h4\",null,{\"id\":\"lazy-slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#lazy-slices\",\"children\":\"Lazy slices\"}]}]\n170:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"Lazy 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.\"}]\n171:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Lazy slices are marked with the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"lazy\"}],\" prop and can include a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"fallback\"}],\" component to display while loading.\"]}]\n1b1:Ta03,
// ./src/templates/home-page.tsx\nimport { Slice } from 'waku';\n\nexport const HomePage = () => {\n  return (\n    <div>\n      <Slice id=\"one\" />\n      <Slice id=\"two\" lazy fallback={<p>Loading...</p>} />\n    </div>\n  );\n};
172:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1b1\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n1b2:T17c1,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { HomePage } from './templates/home-page';\nimport { SliceOne } from './components/slice-one';\nimport { SliceTwo } from './components/slice-two';\n\nconst pages = createPages(async ({ createPage, createSlice }) => [\n  createPage({\n    render: 'static',\n    path: '/',\n    component: HomePage,\n    slices: ['one'], // Note: 'two' is lazy, so it is not included\n  }),\n\n  createSlice({\n    render: 'static',\n    component: SliceOne,\n    id: 'one',\n  }),\n\n  createSlice({\n    render: 'dynamic',\n    component: SliceTwo,\n    id: 'two',\n  }),\n]);\n\nexport default adapter(pages);
173:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1b2\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n174:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"This allows you to have a \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"dynamic\"}],\" slice component while keeping the rest of the page static.\"]}]\n175:[\"$\",\"h4\",null,{\"id\":\"slug-slices\",\"className\":\"mb-2 mt-8 scroll-mt-8 text-balance text-lg font-bold uppercase leading-none tracking-wide text-white sm:text-xl\",\"children\":[\"$\",\"a\",null,{\"href\":\"#slug-slices\",\"children\":\"Slug slices\"}]}]\n176:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":\"Slug slices support dynamic segments in their ID, similar to dynamic route paths. This lets you create a single slice definition that handles many concrete IDs by extracting parameters from the path and passing them as props.\"}]\n177:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Slug slices use \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"render: 'dynamic'\"}],\" and are always loaded lazily, since concrete IDs are not known at build time.\"]}]\n1b3:T5b8,
// ./src/components/tooltip-slice.tsx\n\nexport const TooltipSlice = ({ id }: { id: string }) => {\n  // fetch tooltip data based on id...\n  return <p>Tooltip content for {id}</p>;\n};
178:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1b3\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n1b4:T114f,
// ./src/waku.server.tsx\nimport { createPages } from 'waku';\nimport adapter from 'waku/adapters/default';\n\nimport { HomePage } from './templates/home-page';\nimport { TooltipSlice } from './components/tooltip-slice';\n\nconst pages = createPages(async ({ createPage, createSlice }) => [\n  createPage({\n    render: 'static',\n    path: '/',\n    component: HomePage,\n  }),\n\n  createSlice({\n    render: 'dynamic',\n    component: TooltipSlice,\n    id: 'tooltip/[id]',\n  }),\n]);\n\nexport default adapter(pages);
179:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1b4\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n1b5:Tbe3,
// ./src/templates/home-page.tsx\nimport { Slice } from 'waku';\n\nexport const HomePage = () => {\n  return (\n    <div>\n      <Slice id=\"tooltip/123\" lazy fallback={<p>Loading...</p>} />\n      <Slice id=\"tooltip/456\" lazy fallback={<p>Loading...</p>} />\n    </div>\n  );\n};
17a:[\"$\",\"div\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"$1b5\"},\"className\":\"code mb-16! *:bg-gray-900! max-w-full overflow-clip overflow-x-scroll rounded-xl bg-gray-900 p-4 font-mono text-sm sm:-mx-3 sm:max-w-[calc(100%+1rem)] sm:p-6 sm:text-base\"}]\n17b:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"When \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"\"}],\" is requested, Waku matches it against the \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"tooltip/[id]\"}],\" pattern, extracts \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"{ id: \\\"123\\\" }\"}],\", and passes those params as props to the component.\"]}]\n17c:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"With the file-system router, slug slices work the same way as slug pages — create a file at \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"src/pages/_slices/tooltip/[id].tsx\"}],\" and the ID pattern is derived from the file path.\"]}]\n17d:[\"$\",\"p\",null,{\"className\":\"mb-4 text-pretty text-sm font-normal leading-normal text-white/60 sm:text-lg lg:text-xl\",\"children\":[\"Multiple dynamic segments are also supported (e.g., \",[\"$\",\"span\",null,{\"className\":\"-my-0.5 inline-block rounded-sm bg-gray-900 px-1.5 py-px font-mono text-[13px] text-white/80 sm:text-base sm:group-[.blockquote]:text-sm\",\"children\":\"items/[category]/[id]\"}],\"), and each extracted parameter is passed as a separate prop.\"]}]\n")