Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/intl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ A lightweight internationalization library for Nano Kit applications.
- **Type-safe**. Message schemes are checked against translation data.
- **Composable**. Build messages from small formats like `text`, `params`, `plural`, `match`, and `number`.
- **Tree-shakeable**. Import only the formats your app uses.
- **SSR-ready**. Use `@nano_kit/query` for cached translation loading and dehydration.

## Installation

Expand Down
6 changes: 2 additions & 4 deletions packages/next-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,15 @@ Use `virtualNavigation` and `dehydrate` in `getServerSideProps` to prefetch data
// pages/characters.tsx
import type { GetServerSideProps } from 'next'
import { dehydrate, provide } from '@nano_kit/store'
import { Location$, Navigation$, virtualNavigation } from '@nano_kit/router'
import { LocationNavigation$, virtualNavigation } from '@nano_kit/router'
import { routes } from '@/stores/router'
import { CharactersPage, Stores$ } from '@/ui/pages/Characters'

export const getServerSideProps: GetServerSideProps = async (context) => {
const [$location, navigation] = virtualNavigation(context.resolvedUrl, routes)
const dehydrated = await dehydrate(
Stores$,
[
provide(Location$, $location),
provide(Navigation$, navigation)
provide(LocationNavigation$, virtualNavigation(context.resolvedUrl, routes))
]
)

Expand Down
15 changes: 4 additions & 11 deletions packages/svelte-kit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,16 @@ If your stores use navigation, set up server navigation once in the root server
// src/routes/+layout.server.ts
import { provide } from '@nano_kit/store'
import {
Location$,
Navigation$,
LocationNavigation$,
serverNavigation,
setDehydrationContext
} from '@nano_kit/svelte-kit'
import { routes } from '$lib/routes'
import type { LayoutServerLoad } from './$types'

export const load: LayoutServerLoad = () => {
const [location, navigation] = serverNavigation(routes)

const contextRef = setDehydrationContext([
provide(Location$, location),
provide(Navigation$, navigation)
provide(LocationNavigation$, serverNavigation(routes))
])

return {
Expand All @@ -74,22 +70,19 @@ Set up browser navigation and hydration in the root layout:
<script lang="ts">
import { provide } from '@nano_kit/store'
import {
Location$,
Navigation$,
LocationNavigation$,
Link,
getKitNavigation,
setHydrationContext
} from '@nano_kit/svelte-kit'
import { routes } from '$lib/routes'

let { data, children } = $props()
const [location, navigation] = getKitNavigation(routes)

setHydrationContext({
fromRef: () => data.contextRef,
context: [
provide(Location$, location),
provide(Navigation$, navigation)
provide(LocationNavigation$, getKitNavigation(routes))
]
})
</script>
Expand Down
6 changes: 6 additions & 0 deletions website/astro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ export default defineConfig({
directory: 'router'
}
},
{
label: 'Internationalization',
autogenerate: {
directory: 'intl'
}
},
{
label: 'SSR',
autogenerate: {
Expand Down
10 changes: 7 additions & 3 deletions website/src/content/docs/examples/rick-and-morty.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ sidebar:
order: 4
---

import { Tabs, TabItem } from '@astrojs/starlight/components'
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components'

This example shows a larger encyclopedia app built around routing, data loading, and server-side rendering.

Expand Down Expand Up @@ -49,8 +49,10 @@ Compare the tabs to see the same application model running with React SSR, Svelt
[View source on GitHub](https://github.com/TrigenSoftware/nano_kit/tree/main/examples/rick-and-morty/svelte-kit-nano_kit-ssr)
</TabItem>
<TabItem label='Next.js App Router' icon='vercel'>
<Aside type='caution'>This Next.js example may not always run correctly in the StackBlitz sandbox because of WebContainers runtime differences. If that happens, use the GitHub source locally.</Aside>

<iframe
src='https://codesandbox.io/p/sandbox/github/TrigenSoftware/nano_kit/tree/main/examples/rick-and-morty/next-app-nano_kit-ssr?embed=1&file=%2Fsrc%2Fapp%2Flayout.tsx'
src='https://stackblitz.com/github/TrigenSoftware/nano_kit/tree/main/examples/rick-and-morty/next-app-nano_kit-ssr?embed=1&file=src%2Fapp%2Flayout.tsx&view=both'
loading='lazy'
style='width: 100%; height: 500px; border: 0; border-radius: 4px; overflow: hidden;'
title='Rick and Morty Encyclopedia built with Next.js App Router and Nano Kit'
Expand All @@ -61,8 +63,10 @@ Compare the tabs to see the same application model running with React SSR, Svelt
[View source on GitHub](https://github.com/TrigenSoftware/nano_kit/tree/main/examples/rick-and-morty/next-app-nano_kit-ssr)
</TabItem>
<TabItem label='Next.js Pages Router' icon='vercel'>
<Aside type='caution'>This Next.js example may not always run correctly in the StackBlitz sandbox because of WebContainers runtime differences. If that happens, use the GitHub source locally.</Aside>

<iframe
src='https://codesandbox.io/p/sandbox/github/TrigenSoftware/nano_kit/tree/main/examples/rick-and-morty/next-pages-nano_kit-ssr?embed=1&file=%2Fsrc%2Fpages%2F_app.tsx'
src='https://stackblitz.com/github/TrigenSoftware/nano_kit/tree/main/examples/rick-and-morty/next-pages-nano_kit-ssr?embed=1&file=src%2Fpages%2F_app.tsx&view=both'
loading='lazy'
style='width: 100%; height: 500px; border: 0; border-radius: 4px; overflow: hidden;'
title='Rick and Morty Encyclopedia built with Next.js Pages Router and Nano Kit'
Expand Down
21 changes: 8 additions & 13 deletions website/src/content/docs/integrations/next-router.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Tabs, TabItem, Code } from '@astrojs/starlight/components'

The `@nano_kit/next-router` package provides Next.js integration for `@nano_kit/router`. It re-exports everything from `@nano_kit/react-router` and adds Next.js-specific navigation providers, a router-aware `Link`, and helpers for the Pages Router.

For shared router APIs such as [`useLocation`](/integrations/react-router#uselocation), [`useNavigation`](/integrations/react-router#usenavigation), [`usePaths`](/integrations/react-router#usepaths), [`useListenLinks`](/integrations/react-router#usenavigationlistenlinks--uselistenlinks), see the [React Router integration](/integrations/react-router).
For shared router APIs such as [`useLocation`](/integrations/react-router#uselocation), [`useNavigation`](/integrations/react-router#usenavigation), [`usePaths`](/integrations/react-router#usepaths), [`useListenLinks`](/integrations/react-router#usenavigationlistenlinks-uselistenlinks), see the [React Router integration](/integrations/react-router).

## Installation

Expand Down Expand Up @@ -45,7 +45,7 @@ The Next.js setup differs slightly between the App Router and the Pages Router.

### App Router

Use `NextNavigation` in the root layout to create navigation for the current RSC request and provide `Location$` / `Navigation$` to client components.
Use `NextNavigation` in the root layout to create navigation for the current RSC request and provide `LocationNavigation$` to client components. `Location$` and `Navigation$` are derived from this shared token.

```tsx
import type { Metadata } from 'next'
Expand Down Expand Up @@ -137,17 +137,15 @@ For SSR data loading in `getServerSideProps`, create a virtual navigation for th
```tsx
import type { GetServerSideProps } from 'next'
import { dehydrate, provide } from '@nano_kit/store'
import { Location$, Navigation$, virtualNavigation } from '@nano_kit/next-router'
import { LocationNavigation$, virtualNavigation } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
import CharactersPage, { Stores$ } from '@/ui/pages/Characters'

export const getServerSideProps: GetServerSideProps = async (context) => {
const [$location, navigation] = virtualNavigation(context.resolvedUrl, routes)
const dehydrated = await dehydrate(
Stores$,
[
provide(Location$, $location),
provide(Navigation$, navigation)
provide(LocationNavigation$, virtualNavigation(context.resolvedUrl, routes))
]
)

Expand All @@ -167,7 +165,7 @@ export default function Page() {

### `NextNavigation`

`NextNavigation` is an async RSC component for the App Router. It creates navigation for the current request, provides `Location$` and `Navigation$`, and renders `NextNavigationProvider` on the client.
`NextNavigation` is an async RSC component for the App Router. It creates navigation for the current request, provides `LocationNavigation$`, and renders `NextNavigationProvider` on the client.

Server-side `navigation.push()` and `navigation.replace()` are mapped to Next.js redirects. The `prerenderable` prop skips [Next.js `connection()`](https://nextjs.org/docs/app/api-reference/functions/connection) and allows the route to stay statically prerenderable, but in that mode `searchParams` are not available during server render.

Expand All @@ -190,7 +188,7 @@ export default function Layout({ children }: {

### `NextNavigationProvider`

`NextNavigationProvider` is the client-side provider used by the Pages Router and by `NextNavigation` after hydration. It creates an `InjectionContext` with `Location$` and `Navigation$` only when a fresh client navigation context is needed.
`NextNavigationProvider` is the client-side provider used by the Pages Router and by `NextNavigation` after hydration. It creates an `InjectionContext` with `LocationNavigation$` only when a fresh client navigation context is needed.

```tsx
import { NextNavigationProvider } from '@nano_kit/next-router'
Expand Down Expand Up @@ -234,8 +232,7 @@ These helpers are for the Pages Router.
import type { GetServerSideProps } from 'next'
import { InjectionContext, dehydrate, provide } from '@nano_kit/store'
import {
Location$,
Navigation$,
LocationNavigation$,
notFound,
redirect,
virtualNavigation
Expand All @@ -245,10 +242,8 @@ import { User$ } from '@/stores/user'
import UserPage, { Stores$ } from '@/ui/pages/User'

export const getServerSideProps: GetServerSideProps = async (nextContext) => {
const [$location, navigation] = virtualNavigation(nextContext.resolvedUrl, routes)
const context = new InjectionContext([
provide(Location$, $location),
provide(Navigation$, navigation)
provide(LocationNavigation$, virtualNavigation(nextContext.resolvedUrl, routes))
])
const dehydrated = await dehydrate(Stores$, context)
const { $user } = context.get(User$)
Expand Down
15 changes: 5 additions & 10 deletions website/src/content/docs/integrations/next.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -208,17 +208,15 @@ If the page uses `@nano_kit/router`, create a virtual navigation for the current
```tsx
import type { GetServerSideProps } from 'next'
import { dehydrate, provide } from '@nano_kit/store'
import { Location$, Navigation$, virtualNavigation } from '@nano_kit/next-router'
import { LocationNavigation$, virtualNavigation } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
import CharactersPage, { Stores$ } from '@/ui/pages/Characters'

export const getServerSideProps: GetServerSideProps = async (context) => {
const [$location, navigation] = virtualNavigation(context.resolvedUrl, routes)
const dehydrated = await dehydrate(
Stores$,
[
provide(Location$, $location),
provide(Navigation$, navigation)
provide(LocationNavigation$, virtualNavigation(context.resolvedUrl, routes))
]
)

Expand All @@ -240,21 +238,18 @@ If you want the Pages Router to behave like static dehydration in the App Router
import type { GetServerSideProps } from 'next'
import { dehydrate, provide } from '@nano_kit/store'
import { isFlight } from '@nano_kit/react'
import { Location$, Navigation$, virtualNavigation } from '@nano_kit/next-router'
import { LocationNavigation$, virtualNavigation } from '@nano_kit/next-router'
import { routes } from '@/stores/router'
import CharactersPage, { Stores$ } from '@/ui/pages/Characters'

export const getServerSideProps: GetServerSideProps = async (context) => {
let dehydrated

if (!isFlight(context.req.headers)) {
const [$location, navigation] = virtualNavigation(context.resolvedUrl, routes)

dehydrated = await dehydrate(
Stores$,
[
provide(Location$, $location),
provide(Navigation$, navigation)
provide(LocationNavigation$, virtualNavigation(context.resolvedUrl, routes))
]
)
}
Expand All @@ -279,4 +274,4 @@ Without `@nano_kit/router`, call `dehydrate(Stores$)` without navigation provide
- `@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`](/integrations/next-router#nextnavigation), [`NextNavigationProvider`](/integrations/next-router#nextnavigationprovider), [`Link`](/integrations/next-router#link), or Pages Router helpers such as [`redirect()`](/integrations/next-router#redirect--notfound) and [`notFound()`](/integrations/next-router#redirect--notfound), see [Next.js Router integration](/integrations/next-router).
If you need API-level details for [`NextNavigation`](/integrations/next-router#nextnavigation), [`NextNavigationProvider`](/integrations/next-router#nextnavigationprovider), [`Link`](/integrations/next-router#link), or Pages Router helpers such as [`redirect()`](/integrations/next-router#redirect-notfound) and [`notFound()`](/integrations/next-router#redirect-notfound), see [Next.js Router integration](/integrations/next-router).
8 changes: 4 additions & 4 deletions website/src/content/docs/integrations/preact-router.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ If you want to use DI-based setup, pass the router tokens through `InjectionCont
import { render } from 'preact'
import { provide } from '@nano_kit/store'
import { InjectionContextProvider } from '@nano_kit/preact'
import { App, browserNavigation, router, Location$, Navigation$, Page$, Pages$ } from '@nano_kit/preact-router'
import { App, browserNavigation, router, LocationNavigation$, Page$, Pages$ } from '@nano_kit/preact-router'
import { pages } from './pages'
import { routes } from './routes'

Expand All @@ -84,15 +84,15 @@ declare module '@nano_kit/router' {
}

/* Create navigation and page accessor */
const [$location, navigation] = browserNavigation(routes)
const locationNavigation = browserNavigation(routes)
const [$location] = locationNavigation
const $page = router($location, pages)

/* Render App with DI */
render((
<InjectionContextProvider
context={[
provide(Location$, $location),
provide(Navigation$, navigation),
provide(LocationNavigation$, locationNavigation),
provide(Page$, $page) // required for App component
]}
>
Expand Down
30 changes: 24 additions & 6 deletions website/src/content/docs/integrations/preact-ssr.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ import { Steps, Tabs, TabItem, Code, Aside } from '@astrojs/starlight/components
app.get('*', async (req, res) => {
const result = await renderer.render(req.url, {
cookie: req.headers.cookie,
acceptLanguage: req.headers['accept-language']
acceptLanguage: req.headers['accept-language'],
userAgent: req.headers['user-agent']
})

if (result.setCookieHeaders) {
Expand All @@ -115,23 +116,40 @@ The SSR plugin can provide request-bound browser-like dependencies for stores th

### `cookieStore`

Use `cookieStore: true` when the renderer should provide a request-bound `CookieStore$` during SSR. This feature uses the optional peer package `@nano_kit/platform-web`, so install it in the app before enabling the option. See [SSR Cookies](/ssr/cookies) for the full setup, including store code and `Set-Cookie` forwarding.
Use `inject.cookieStore: true` when the renderer should provide a request-bound `CookieStore$` during SSR. This feature uses the optional peer package `@nano_kit/platform-web`, so install it in the app before enabling the option. See [SSR Cookies](/ssr/cookies) for the full setup, including store code and `Set-Cookie` forwarding.

```js
ssr({
index: 'src/index.tsx',
cookieStore: true
inject: {
cookieStore: true
}
})
```

### `browserLocale`

Use `browserLocale: true` when the renderer should provide request-bound `Locales$` during SSR. This feature uses the optional peer package `@nano_kit/platform-web`, so install it in the app before enabling the option. The renderer parses the incoming `Accept-Language` header with [`parseLocales`](/platform/web/#locale), so universal stores can inject `Locales$` and resolve the same locale shape on the server and in the browser. See [SSR Locale](/ssr/locale) for the full setup.
Use `inject.browserLocale: true` when the renderer should provide request-bound `Locales$` during SSR. This feature uses the optional peer package `@nano_kit/platform-web`, so install it in the app before enabling the option. The renderer parses the incoming `Accept-Language` header with [`parseLocales`](/platform/web/#locale), so universal stores can inject `Locales$` and resolve the same locale shape on the server and in the browser. See [SSR Locale](/ssr/locale) for the full setup.

```js
ssr({
index: 'src/index.tsx',
browserLocale: true
inject: {
browserLocale: true
}
})
```

### `userAgent`

Use `inject.userAgent: true` when the renderer should provide request-bound `UserAgent$` during SSR. This feature uses the optional peer package `@nano_kit/platform-web`, so install it in the app before enabling the option. The renderer reads the incoming `User-Agent` header, so universal stores can inject `UserAgent$` and read a user agent string on the server and in the browser.

```js
ssr({
index: 'src/index.tsx',
inject: {
userAgent: true
}
})
```

Expand All @@ -154,7 +172,7 @@ class AppRenderer extends PreactRenderer {
export const renderer = new AppRenderer({
base: import.meta.env.BASE_URL,
manifestPath: import.meta.env.MANIFEST,
cookieStore: import.meta.env.SSR_COOKIE_STORE,
inject: import.meta.env.SSR_INJECT,
routes,
pages
})
Expand Down
8 changes: 4 additions & 4 deletions website/src/content/docs/integrations/react-router.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ If you want to use DI-based setup, pass the router tokens through `InjectionCont
import { createRoot } from 'react-dom/client'
import { provide } from '@nano_kit/store'
import { InjectionContextProvider } from '@nano_kit/react'
import { App, browserNavigation, router, Location$, Navigation$, Page$, Pages$ } from '@nano_kit/react-router'
import { App, browserNavigation, router, LocationNavigation$, Page$, Pages$ } from '@nano_kit/react-router'
import { pages } from './pages'
import { routes } from './routes'

Expand All @@ -84,15 +84,15 @@ declare module '@nano_kit/router' {
}

/* Create navigation and page accessor */
const [$location, navigation] = browserNavigation(routes)
const locationNavigation = browserNavigation(routes)
const [$location] = locationNavigation
const $page = router($location, pages)

/* Render App with DI */
createRoot(document.getElementById('root')!).render(
<InjectionContextProvider
context={[
provide(Location$, $location),
provide(Navigation$, navigation),
provide(LocationNavigation$, locationNavigation),
provide(Page$, $page) // required for App component
]}
>
Expand Down
Loading