Skip to content

Next.js

There is no dedicated package for Next.js apps. Use @nano_kit/react for signals, Dependency Injection, and hydration. If your app also uses @nano_kit/router, add @nano_kit/next-router.

For detailed API reference, see the React integration and Next.js Router integration.

Install @nano_kit/store and @nano_kit/react for the base React integration. Add @nano_kit/router and @nano_kit/next-router only when you use Nano Kit router.

pnpm add @nano_kit/store @nano_kit/react
# If you use Nano Kit router:
pnpm add @nano_kit/router @nano_kit/next-router

In the App Router there are two different setups:

  • Flight dehydration: use Dehydration when server dehydration should run both on the initial request and on RSC flight navigations.
  • Static dehydration: use StaticDehydration when dehydration should run only on the initial full-page request. During client-side navigations, stores will fetch data in the browser instead of on the server.

Both dehydration components can be nested and repeated. It is valid to dehydrate shared stores in a layout and page-specific stores in a page.

In this mode, FlightDetector is not needed. Keep HydrationProvider in the root layout. If you use @nano_kit/router, wrap the layout with NextNavigation. Without the router, omit NextNavigation.

import type { Metadata } from 'next'
import { HydrationProvider } from '@nano_kit/react'
import { NextNavigation } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
/* Define global routes types */
declare module '@nano_kit/router' {
interface AppContext {
routes: typeof routes
}
}
export const metadata: Metadata = {
title: 'My App'
}
export default function RootLayout({ children }: {
children: React.ReactNode
}) {
return (
<NextNavigation routes={routes}>
<HydrationProvider>
<html lang='en'>
<body>{children}</body>
</html>
</HydrationProvider>
</NextNavigation>
)
}

In page components, use Dehydration. If the page uses @nano_kit/router, wrap the dehydration boundary in its own NextNavigation so flight renders still receive the current navigation context.

import { Dehydration } from '@nano_kit/react'
import { NextNavigation } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
import CharactersPage, { Stores$ } from '@/ui/pages/Characters'
export default function Page() {
return (
<NextNavigation routes={routes}>
<Dehydration stores={Stores$}>
<CharactersPage />
</Dehydration>
</NextNavigation>
)
}

If you do not use @nano_kit/router, render Dehydration directly without NextNavigation.

In this mode, add FlightDetector to the root layout so StaticDehydration can distinguish the initial full-page request from RSC flight navigations. Keep HydrationProvider in the layout as well.

If you use @nano_kit/router, wrap the layout with NextNavigation. Without the router, omit NextNavigation.

import type { Metadata } from 'next'
import {
FlightDetector,
HydrationProvider
} from '@nano_kit/react'
import { NextNavigation } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
/* Define global routes types */
declare module '@nano_kit/router' {
interface AppContext {
routes: typeof routes
}
}
export const metadata: Metadata = {
title: 'My App'
}
export default function RootLayout({ children }: {
children: React.ReactNode
}) {
return (
<FlightDetector>
<NextNavigation routes={routes}>
<HydrationProvider>
<html lang='en'>
<body>{children}</body>
</html>
</HydrationProvider>
</NextNavigation>
</FlightDetector>
)
}

In page components, use StaticDehydration. If the page uses @nano_kit/router, wrap the dehydration boundary in its own NextNavigation.

import { StaticDehydration } from '@nano_kit/react'
import { NextNavigation } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
import CharactersPage, { Stores$ } from '@/ui/pages/Characters'
export default function Page() {
return (
<NextNavigation routes={routes}>
<StaticDehydration stores={Stores$}>
<CharactersPage />
</StaticDehydration>
</NextNavigation>
)
}

If you do not use @nano_kit/router, render StaticDehydration directly without NextNavigation.

In the Pages Router, the usual setup is:

If you use @nano_kit/router, wrap the app with NextNavigationProvider. Without the router, keep only HydrationProvider.

import type { AppProps } from 'next/app'
import { HydrationProvider } from '@nano_kit/react'
import { NextNavigationProvider } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
/* Define global routes types */
declare module '@nano_kit/router' {
interface AppContext {
routes: typeof routes
}
}
export default function App({ Component, pageProps }: AppProps) {
return (
<NextNavigationProvider routes={routes}>
<HydrationProvider dehydrated={pageProps.dehydrated}>
<Component {...pageProps} />
</HydrationProvider>
</NextNavigationProvider>
)
}

When you need server-prefetched store data, dehydrate stores in getServerSideProps and pass the snapshot through pageProps.dehydrated.

If the page uses @nano_kit/router, create the dehydration context with virtualNavigationContext.

import type { GetServerSideProps } from 'next'
import { dehydrate } from '@nano_kit/store'
import { virtualNavigationContext } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
import CharactersPage, { Stores$ } from '@/ui/pages/Characters'
export const getServerSideProps: GetServerSideProps = async (context) => {
const dehydrated = await dehydrate(
Stores$,
virtualNavigationContext(context.resolvedUrl, routes)
)
return {
props: {
dehydrated
}
}
}
export default function Page() {
return <CharactersPage />
}

If you want the Pages Router to behave like static dehydration in the App Router, use isFlight(context.req.headers) and skip dehydration during client-side navigations. In that case, stores will fetch data in the browser instead of on the server.

import type { GetServerSideProps } from 'next'
import { dehydrate } from '@nano_kit/store'
import { isFlight } from '@nano_kit/react'
import { virtualNavigationContext } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
import CharactersPage, { Stores$ } from '@/ui/pages/Characters'
export const getServerSideProps: GetServerSideProps = async (context) => {
const dehydrated = !isFlight(context.req.headers) && await dehydrate(
Stores$,
virtualNavigationContext(context.resolvedUrl, routes)
)
return {
props: {
dehydrated
}
}
}
export default function Page() {
return <CharactersPage />
}

Without @nano_kit/router, call dehydrate(Stores$) without virtualNavigationContext.

  • @nano_kit/react handles React bindings, DI integration, HydrationProvider, Dehydration, StaticDehydration, and FlightDetector.
  • @nano_kit/next-router is only needed when the app uses @nano_kit/router inside Next.js.
  • @nano_kit/router remains the place where routes, pages, layouts, and navigation logic are declared.

If you need API-level details for NextNavigation, NextNavigationProvider, Link, or Pages Router helpers such as redirect() and notFound(), see Next.js Router integration.