createNavigation
createNavigation(indicators: Indicators, options?: NavigationOptions): Navigation
createLink(indicators: Indicators, options?: NavigationOptions): Navigation['Link']
Links and hooks that know which locale the page is in. Build them once, in a client module, and export them:
// lib/link.ts
'use client'
import { createNavigation } from '@fairgarden/indicators/link'
import { mountPrefix } from '@fairgarden/monolith/link'
import { indicators } from '@fairgarden/id/lib/indicators'
export const { Link, useHref, useIndicators, useLocale, useSetLocale, usePref } = createNavigation(
indicators,
{ mount: mountPrefix('@fairgarden/id') }
)
The module has to be a client module, because the pieces use hooks and a
server component can only render what a client module exports, not call
it. Server components import Link from the same file and render it as
usual.
Options
| Field | Default | Meaning |
| --- | --- | --- |
| mount | '' | the prefix the app is mounted at, applied after the locale |
| cookiePath | '/' | the path a cookie written by usePref is scoped to |
mount is what mountPrefix from @fairgarden/monolith/link returns:
/id inside a monolith and '' standalone, so the same module works either
way. See In a monolith.
Link
next/link with two more things done to href: the page's locale is
prefixed by the localePrefix rules, then the mount.
Props are next/link's, plus locale to link into another locale.
<Link href="/settings">Settings</Link>
// English page, standalone: /settings
// French page, mounted at /id: /id/fr/settings
<Link href="/" locale="fr">Français</Link>
A locale given explicitly is a choice, so following that link also writes
the locale cookie, which the site root reads next time.
A link that merely crosses locales — to a page that exists in one locale
only — is not a choice; give it remember={false}. Your own onClick runs
first, and calling preventDefault there skips the cookie along with the
navigation.
Absolute URLs, fragments, relative paths, a path that already starts with a
locale, and a path starting with an excluded segment such as /api are left
alone. A file in public/ is not known to the client, so link to it with
<a> or name it in the config's exclude.
useHref
The same computation as a function, for router.push and anything else that
ends up as a string:
const toHref = useHref()
router.push(toHref('/settings'))
toHref('/settings', 'fr')
useSetLocale
const setLocale = useSetLocale()
setLocale('fr') // cookie locale=fr, then router.push('/fr/<this page>')
Moves to the page the user is on, in another locale, and remembers the
choice. The page comes from usePathname, which is the public URL; the
mount and the old locale prefix are stripped and the query and fragment are
kept, so /id/settings?tab=2 becomes /id/fr/settings?tab=2. To move a
user into a locale without recording a choice, build the href with
useHref and push it yourself.
useIndicators and useLocale
const { locale, prefs, flags } = useIndicators()
const locale = useLocale()
Decoded from useParams(), which holds the internal path's params. Outside
the locale tree — a global not-found page, say — the locale is the default
and the preferences and flags are empty.
usePref
const [theme, setTheme] = usePref('theme')
setTheme('dark') // cookie theme=dark, then router.refresh()
setTheme(undefined) // clears it
Writes the cookie the preference is read from — Path=/, SameSite=Lax,
Secure on https, Max-Age from the indicator's maxAge — and refreshes
the route, so the next render is the variant the cookie now selects. Only a
cookie-backed preference can be set this way; the setter throws for any
other.
Notes
The locale comes from the route params, not from a provider, so nothing has
to wrap the tree and a Link in the root layout's own chrome works. The
config comes from the module that calls createNavigation, so it is bundled
for the client; keep lib/indicators.ts free of server-only imports.