fimo 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-templates/broken-link-sweeper/capabilities.yaml +0 -4
- package/assets/agent-templates/content-translator/capabilities.yaml +0 -2
- package/assets/agent-templates/seo-audit/capabilities.yaml +0 -1
- package/assets/content-templates/types-template.eta +13 -7
- package/assets/skills/fimo/SKILL.md +2 -2
- package/assets/skills/fimo-cli/SKILL.md +8 -8
- package/assets/skills/fimo-cli/references/agent-setup.md +1 -1
- package/assets/skills/fimo-cli/references/agents.md +5 -12
- package/assets/skills/fimo-cli/references/assets.md +2 -2
- package/assets/skills/fimo-cli/references/content.md +16 -30
- package/assets/skills/fimo-cli/references/describe.md +1 -1
- package/assets/skills/fimo-cli/references/forms.md +8 -8
- package/assets/skills/fimo-cli/references/init.md +5 -4
- package/assets/skills/fimo-cli/references/labels.md +4 -4
- package/assets/skills/fimo-cli/references/locales.md +3 -3
- package/assets/skills/fimo-cli/references/project-vars-and-secrets.md +1 -1
- package/assets/skills/fimo-code/SKILL.md +40 -50
- package/assets/skills/fimo-code/references/assets.md +6 -4
- package/assets/skills/fimo-code/references/content/clients.md +130 -0
- package/assets/skills/fimo-code/references/content/primitives.md +5 -0
- package/assets/skills/fimo-code/references/content/richtext.md +48 -0
- package/assets/skills/fimo-code/references/content/schema.md +122 -0
- package/assets/skills/fimo-code/references/content.md +12 -597
- package/assets/skills/fimo-code/references/design.md +1 -1
- package/assets/skills/fimo-code/references/frameworks/next/app-router.md +116 -0
- package/assets/skills/fimo-code/references/frameworks/next/locales.md +59 -0
- package/assets/skills/fimo-code/references/frameworks/next/pages-router.md +99 -0
- package/assets/skills/fimo-code/references/frameworks/next.md +116 -0
- package/assets/skills/fimo-code/references/frameworks/react-router.md +295 -0
- package/assets/skills/fimo-code/references/frameworks/vite-react.md +225 -0
- package/assets/skills/fimo-code/references/labels.md +24 -83
- package/assets/skills/fimo-code/references/locales.md +22 -111
- package/assets/skills/fimo-code/references/setup-plain-vite.md +3 -227
- package/assets/skills/fimo-code/references/setup-react-router.md +3 -276
- package/assets/skills/fimo-code/references/ui.md +32 -78
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +16 -16
- package/dist/scripts/extract-translations.js +8 -1
- package/package.json +1 -1
- package/templates/react-router/AGENTS.md +10 -10
- package/templates/react-router/package.json +1 -1
- package/templates/react-router/react-router.config.ts +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Next.js App Router
|
|
2
|
+
|
|
3
|
+
Preserve the project's existing App Router structure. Fimo does not require a route group, `[locale]` segment, middleware, or a specific rendering mode.
|
|
4
|
+
|
|
5
|
+
## Root provider
|
|
6
|
+
|
|
7
|
+
Resolve the locale using the application's existing strategy, fetch labels on the server, and pass the snapshot into the client provider:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { getLabels } from 'fimo/next';
|
|
11
|
+
import { FimoProvider } from 'fimo/next/client';
|
|
12
|
+
|
|
13
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
14
|
+
const locale = 'en'; // replace with the app's own resolver
|
|
15
|
+
const labels = await getLabels({ locale });
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<html lang={locale}>
|
|
19
|
+
<body>
|
|
20
|
+
<FimoProvider labels={labels.snapshot}>{children}</FimoProvider>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Do not hardcode a locale when the app already resolves one from route params, a cookie, headers/domain, middleware, or an i18n library. Load `references/frameworks/next/locales.md` for those cases.
|
|
28
|
+
|
|
29
|
+
## Static generation and ISR
|
|
30
|
+
|
|
31
|
+
Server Components may read generated clients directly. Omit `locale` to use the configured default or bind the application-resolved locale once with `createFimo`:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { createFimo, RichText, Text } from 'fimo/next';
|
|
35
|
+
import { content } from '@/schemas';
|
|
36
|
+
|
|
37
|
+
export const revalidate = 60;
|
|
38
|
+
|
|
39
|
+
export default async function ArticlesPage() {
|
|
40
|
+
const fimo = createFimo({ content });
|
|
41
|
+
const page = await fimo.articles.list({ sort: '-publishedAt', limit: 12 });
|
|
42
|
+
|
|
43
|
+
return page.items.map((article) => (
|
|
44
|
+
<article key={`${article.id}:${article.locale}`}>
|
|
45
|
+
<Text value={article.title} as="h2" />
|
|
46
|
+
<RichText value={article.body} />
|
|
47
|
+
</article>
|
|
48
|
+
));
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`createFimo({ content, locale })` is a typed locale-bound facade over the generated registry. It does not own routing and does not force SSG.
|
|
53
|
+
|
|
54
|
+
Static builds that read Fimo must receive the real build-time `FIMO_API_URL` (or `NEXT_PUBLIC_FIMO_API_URL`) and default locale environment. Do not switch a route to dynamic rendering merely to hide a missing build environment; choose SSG, ISR, or request-time rendering from the application's intended behavior.
|
|
55
|
+
|
|
56
|
+
## Request-time rendering
|
|
57
|
+
|
|
58
|
+
Use normal Next request APIs and pass the resulting locale/query values to Fimo:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { headers } from 'next/headers';
|
|
62
|
+
import { createFimo, Text } from 'fimo/next';
|
|
63
|
+
import { content } from '@/schemas';
|
|
64
|
+
|
|
65
|
+
export const dynamic = 'force-dynamic';
|
|
66
|
+
|
|
67
|
+
export default async function Page() {
|
|
68
|
+
const locale = (await headers()).get('x-locale') ?? 'en';
|
|
69
|
+
const fimo = createFimo({ content, locale });
|
|
70
|
+
const settings = await fimo.settings.get();
|
|
71
|
+
return <Text value={settings.siteName} as="h1" />;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This example demonstrates request-time rendering, not a recommended locale policy. Preserve the application's actual resolver.
|
|
76
|
+
|
|
77
|
+
## Client Components
|
|
78
|
+
|
|
79
|
+
Client Components use the generated `.client` module and `fimo/next/client`:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
'use client';
|
|
83
|
+
|
|
84
|
+
import { Text, useLabels } from 'fimo/next/client';
|
|
85
|
+
import * as Article from '@/schemas/Article.client';
|
|
86
|
+
|
|
87
|
+
export function FeaturedArticles() {
|
|
88
|
+
const { t } = useLabels();
|
|
89
|
+
const { data: page, isLoading } = Article.useList({ where: { featured: true }, limit: 3 });
|
|
90
|
+
|
|
91
|
+
if (isLoading || !page) return null;
|
|
92
|
+
return (
|
|
93
|
+
<section>
|
|
94
|
+
<Text value={t('articles.featured')} as="h2" />
|
|
95
|
+
{page.items.map((article) => (
|
|
96
|
+
<Text key={article.id} value={article.title} as="h3" />
|
|
97
|
+
))}
|
|
98
|
+
</section>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Do not add `'use client'` to a page solely because Fimo is present. Keep promise reads and server primitives in Server Components; introduce a Client Component only for browser state, events, or hooks.
|
|
104
|
+
|
|
105
|
+
## Metadata
|
|
106
|
+
|
|
107
|
+
Next remains in control through `generateMetadata`. Resolve labels/content on the server and convert tracked strings only where Next expects primitives:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
export async function generateMetadata() {
|
|
111
|
+
const labels = await getLabels();
|
|
112
|
+
return { title: String(labels.t('seo.home.title')) };
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Do not expect `FimoProvider` to inject SEO, routes, language, or direction.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Next.js locale integration
|
|
2
|
+
|
|
3
|
+
The application resolves locale; Fimo consumes it. Preserve existing route segments, middleware, cookies, domains, headers, redirects, language switchers, and i18n libraries.
|
|
4
|
+
|
|
5
|
+
## Server reads
|
|
6
|
+
|
|
7
|
+
Pass the resolved locale to the server facade and label reader:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const fimo = createFimo({ content, locale });
|
|
11
|
+
const [page, labels] = await Promise.all([fimo.articles.list({ limit: 12 }), getLabels({ locale })]);
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
If omitted, both use the configured default locale. Avoid request-global locale state; server code should receive or resolve locale at the request/route boundary.
|
|
15
|
+
|
|
16
|
+
## Client context
|
|
17
|
+
|
|
18
|
+
Pass the same locale through `FimoProvider`. A matching label snapshot supplies it automatically:
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
const labels = await getLabels({ locale });
|
|
22
|
+
return <FimoProvider labels={labels.snapshot}>{children}</FimoProvider>;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
When labels are not needed at that boundary, use `<FimoProvider locale={locale}>` so generated client hooks still query the correct locale.
|
|
26
|
+
|
|
27
|
+
## Existing strategies
|
|
28
|
+
|
|
29
|
+
- App Router route segment: read `params.locale` in the relevant layout/page.
|
|
30
|
+
- Next locale middleware or library: reuse its resolved locale.
|
|
31
|
+
- Cookie/user preference: read it using Next's request API in request-time code.
|
|
32
|
+
- Domain/header routing: resolve from the incoming request or middleware result.
|
|
33
|
+
- Pages Router i18n: reuse `context.locale` in data functions and `router.locale`/page props on the client.
|
|
34
|
+
- Default-only app: omit locale arguments and let Fimo use `.fimo/config.json#i18n.defaultLocale` (fallback `en`).
|
|
35
|
+
|
|
36
|
+
With `next-i18next`, keep its routing and translation provider for the strings it owns. Pass the same resolved locale to Fimo; do not replace the library or duplicate its route structure.
|
|
37
|
+
|
|
38
|
+
Before creating non-default Fimo labels or content variants, confirm that those locales are enabled for the linked Fimo project. `.fimo/config.json` documents the application's locale policy, but `fimo validate` does not currently add project locales.
|
|
39
|
+
|
|
40
|
+
## Ownership boundary
|
|
41
|
+
|
|
42
|
+
The application owns:
|
|
43
|
+
|
|
44
|
+
- which URL maps to which locale;
|
|
45
|
+
- locale detection and switching;
|
|
46
|
+
- redirects and canonical URLs;
|
|
47
|
+
- `<html lang>` and `dir`;
|
|
48
|
+
- localized Next metadata.
|
|
49
|
+
|
|
50
|
+
Fimo owns:
|
|
51
|
+
|
|
52
|
+
- configured locale/default-locale policy;
|
|
53
|
+
- localized content variants and labels;
|
|
54
|
+
- client content/label context after receiving the locale;
|
|
55
|
+
- editable source metadata.
|
|
56
|
+
|
|
57
|
+
Do not add `.fimo/config.json#routes` to a Next project yet. The current route-metadata validator expects React Router's `src/routes.ts`; a first-class Next Studio catalog and preview bridge are separate product work. Do not create a fake `src/routes.ts` to satisfy that check.
|
|
58
|
+
|
|
59
|
+
Do not introduce `[locale]` segments, cookie routing, or domain routing unless the product request requires that URL strategy. Do not add a second locale provider when the existing one can supply the resolved locale to Fimo.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Next.js Pages Router
|
|
2
|
+
|
|
3
|
+
Preserve `pages/` or `src/pages/`. Pages Router sends props through JSON serialization, while Fimo read values carry classes/symbol metadata. Use generated `serialize()` and `.client` `hydrate()` at that boundary so rendered values remain editable.
|
|
4
|
+
|
|
5
|
+
## Provider in `_app`
|
|
6
|
+
|
|
7
|
+
Pass each page's label snapshot to one provider:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { FimoProvider, type LabelsSnapshot } from 'fimo/next/client';
|
|
11
|
+
import type { AppProps } from 'next/app';
|
|
12
|
+
|
|
13
|
+
type FimoPageProps = { fimoLabels: LabelsSnapshot };
|
|
14
|
+
|
|
15
|
+
export default function App({ Component, pageProps }: AppProps<FimoPageProps>) {
|
|
16
|
+
return (
|
|
17
|
+
<FimoProvider labels={pageProps.fimoLabels}>
|
|
18
|
+
<Component {...pageProps} />
|
|
19
|
+
</FimoProvider>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If the application already has providers, compose Fimo with them; do not replace the tree.
|
|
25
|
+
|
|
26
|
+
## Static generation and ISR
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { createFimo, getLabels } from 'fimo/next';
|
|
30
|
+
import type { GetStaticProps, InferGetStaticPropsType } from 'next';
|
|
31
|
+
|
|
32
|
+
import { ArticleView } from '@/components/ArticleView';
|
|
33
|
+
import { content } from '@/schemas';
|
|
34
|
+
import * as ArticleClient from '@/schemas/Article.client';
|
|
35
|
+
|
|
36
|
+
export const getStaticProps = (async ({ locale }) => {
|
|
37
|
+
const fimo = createFimo({ content, locale });
|
|
38
|
+
const [page, labels] = await Promise.all([
|
|
39
|
+
fimo.articles.list({ sort: '-publishedAt', limit: 1 }),
|
|
40
|
+
getLabels({ locale }),
|
|
41
|
+
]);
|
|
42
|
+
const article = page.items[0];
|
|
43
|
+
if (!article) return { notFound: true };
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
props: {
|
|
47
|
+
article: fimo.articles.serialize(article),
|
|
48
|
+
fimoLabels: labels.snapshot,
|
|
49
|
+
},
|
|
50
|
+
revalidate: 60,
|
|
51
|
+
};
|
|
52
|
+
}) satisfies GetStaticProps;
|
|
53
|
+
|
|
54
|
+
export default function Page({ article: serialized }: InferGetStaticPropsType<typeof getStaticProps>) {
|
|
55
|
+
return <ArticleView article={ArticleClient.hydrate(serialized)} />;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`serialize()` is not an editing workaround or a routing API. It converts source-tracked values to JSON-safe props. `hydrate()` restores those values after Next deserializes them.
|
|
60
|
+
|
|
61
|
+
## Request-time rendering
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { getLabels } from 'fimo/next';
|
|
65
|
+
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
|
|
66
|
+
import * as Article from '@/schemas/Article';
|
|
67
|
+
import * as ArticleClient from '@/schemas/Article.client';
|
|
68
|
+
|
|
69
|
+
export const getServerSideProps = (async ({ params, locale }) => {
|
|
70
|
+
const [article, labels] = await Promise.all([
|
|
71
|
+
Article.getBySlug(String(params?.slug), { locale }),
|
|
72
|
+
getLabels({ locale }),
|
|
73
|
+
]);
|
|
74
|
+
return article ? { props: { article: Article.serialize(article), fimoLabels: labels.snapshot } } : { notFound: true };
|
|
75
|
+
}) satisfies GetServerSideProps;
|
|
76
|
+
|
|
77
|
+
export default function ArticlePage({ article }: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
|
78
|
+
return <ArticleView article={ArticleClient.hydrate(article)} />;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use the locale/router values Pages Router already supplies. Do not introduce App Router conventions.
|
|
83
|
+
|
|
84
|
+
## Client hooks
|
|
85
|
+
|
|
86
|
+
Interactive components import hooks and primitives from the client surfaces:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { Text, useLabels } from 'fimo/next/client';
|
|
90
|
+
import * as Article from '@/schemas/Article.client';
|
|
91
|
+
|
|
92
|
+
export function SearchResults() {
|
|
93
|
+
const { t } = useLabels();
|
|
94
|
+
const { data } = Article.useList({ limit: 12 });
|
|
95
|
+
return <Text value={t('search.title')} as="h1" />;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Do not serialize data that never crosses `getStaticProps`/`getServerSideProps`. Do not hydrate plain API JSON or data already returned by a client hook.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Next.js integration
|
|
2
|
+
|
|
3
|
+
Use this reference when `fimo describe --json` reports `framework.slug: "nextjs"`, or the project uses an App Router (`app/` or `src/app/`) or Pages Router (`pages/` or `src/pages/`) tree.
|
|
4
|
+
|
|
5
|
+
Fimo supports schema-first content in both routers across static generation, ISR, request-time rendering, and Client Components. The application owns routes, locale resolution, links, `<html lang/dir>`, and SEO. Fimo receives the resolved locale and supplies content, labels, source metadata, and client editing context.
|
|
6
|
+
|
|
7
|
+
## Choose the router reference
|
|
8
|
+
|
|
9
|
+
- `app/` or `src/app/` → `references/frameworks/next/app-router.md`
|
|
10
|
+
- `pages/` or `src/pages/` → `references/frameworks/next/pages-router.md`
|
|
11
|
+
- Existing locale library, middleware, cookies, domains, or locale route segments → also load `references/frameworks/next/locales.md`
|
|
12
|
+
|
|
13
|
+
When streaming a nested reference from the CLI, preserve its slashes: use `--ref frameworks/next/app-router`, `--ref frameworks/next/pages-router`, or `--ref frameworks/next/locales`. Do not flatten those IDs.
|
|
14
|
+
|
|
15
|
+
Preserve the router already present. Do not add a second router or convert between App and Pages Router merely to integrate Fimo.
|
|
16
|
+
|
|
17
|
+
## Client dependency
|
|
18
|
+
|
|
19
|
+
Server-only Fimo reads need no client data library. If the app uses `FimoProvider`,
|
|
20
|
+
`useLabels`, or generated `.client` hooks, add `@tanstack/react-query` using the
|
|
21
|
+
project's package manager. It is an optional `fimo` peer so server-only projects
|
|
22
|
+
do not install it automatically.
|
|
23
|
+
|
|
24
|
+
## Runtime URL
|
|
25
|
+
|
|
26
|
+
Linked projects need the tenant content API in both Next runtimes:
|
|
27
|
+
|
|
28
|
+
```dotenv
|
|
29
|
+
FIMO_API_URL=https://<tenant-api>
|
|
30
|
+
NEXT_PUBLIC_FIMO_API_URL=https://<tenant-api>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Current `fimo init` and `fimo clone` write these alongside `VITE_API_URL` while
|
|
34
|
+
preserving user-owned values. Never point them at the management API. Keep the
|
|
35
|
+
server variable private; only the public variable is compiled into Client
|
|
36
|
+
Components.
|
|
37
|
+
|
|
38
|
+
## Generate the content surface
|
|
39
|
+
|
|
40
|
+
Author `src/schemas/<Uid>.json`, then run `fimo schemas push <Uid>`. The push validates the schema, updates the linked environment, and generates:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
src/schemas/
|
|
44
|
+
├── Article.json
|
|
45
|
+
├── Article.types.ts
|
|
46
|
+
├── Article.ts
|
|
47
|
+
├── Article.client.ts
|
|
48
|
+
└── index.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `<Uid>.ts` is server-safe and exposes promise reads plus `serialize()`.
|
|
52
|
+
- `<Uid>.client.ts` starts with `'use client'`, exposes React Query hooks plus `hydrate()`.
|
|
53
|
+
- `index.ts` exports the generated `content` registry used by `createFimo`.
|
|
54
|
+
- `.types.ts` is an implementation detail; import public types from `<Uid>.ts` or `<Uid>.client.ts`.
|
|
55
|
+
|
|
56
|
+
Never write the registry by hand and never create alternate `*.schema.json` filenames.
|
|
57
|
+
|
|
58
|
+
## Server and client boundaries
|
|
59
|
+
|
|
60
|
+
Server Components, route handlers, `getStaticProps`, and `getServerSideProps` import from `fimo/next` and generated server modules:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { createFimo, getLabels, Text } from 'fimo/next';
|
|
64
|
+
import { content } from '@/schemas';
|
|
65
|
+
|
|
66
|
+
const fimo = createFimo({ content, locale });
|
|
67
|
+
const [articles, labels] = await Promise.all([
|
|
68
|
+
fimo.articles.list({ sort: '-publishedAt', limit: 12 }),
|
|
69
|
+
getLabels({ locale }),
|
|
70
|
+
]);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Client Components import from `fimo/next/client` and generated `.client` modules:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
'use client';
|
|
77
|
+
|
|
78
|
+
import { Text, useLabels } from 'fimo/next/client';
|
|
79
|
+
import * as Article from '@/schemas/Article.client';
|
|
80
|
+
|
|
81
|
+
export function ArticleList() {
|
|
82
|
+
const { t } = useLabels();
|
|
83
|
+
const { data } = Article.useList({ limit: 12 });
|
|
84
|
+
return <Text value={t('articles.title')} as="h1" />;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Do not import `fimo/next/client` into server-only modules. Do not import React Query hooks from `<Uid>.ts`.
|
|
89
|
+
|
|
90
|
+
## Provider boundary
|
|
91
|
+
|
|
92
|
+
Client hooks need one `FimoProvider` near the application root. The server may fetch a label snapshot and pass it across that boundary:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { getLabels } from 'fimo/next';
|
|
96
|
+
import { FimoProvider } from 'fimo/next/client';
|
|
97
|
+
|
|
98
|
+
const labels = await getLabels({ locale });
|
|
99
|
+
return <FimoProvider labels={labels.snapshot}>{children}</FimoProvider>;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Passing `locale` without `labels` is valid when only content hooks need context. Passing a matching snapshot supplies both locale and labels. Never teach the unreleased `FimoNextProvider` name; the public component is `FimoProvider`.
|
|
103
|
+
|
|
104
|
+
## Rendering modes
|
|
105
|
+
|
|
106
|
+
- Static/ISR: promise reads run during build/revalidation. App Router uses `revalidate`; Pages Router returns `revalidate` from `getStaticProps`.
|
|
107
|
+
- Request-time: App Router uses its normal request APIs/dynamic route policy; Pages Router uses `getServerSideProps`.
|
|
108
|
+
- Client reads: generated `.client` hooks use React Query under `FimoProvider`.
|
|
109
|
+
- Pages Router props must be JSON-safe; use generated `serialize()` before returning props and generated `.client` `hydrate()` in the page.
|
|
110
|
+
- App Router Server Components do not need serialize/hydrate for values that stay on the server-rendered side.
|
|
111
|
+
|
|
112
|
+
## Greenfield boundary
|
|
113
|
+
|
|
114
|
+
Use normal schemas, generated clients, Fimo primitives, and labels. Do not use migration-only `Wrapped*` components, private `fetchEntries`, fallback stores, or adopted-site loaders. Migration code may temporarily adapt old source shapes, but those interfaces are not the Next SDK.
|
|
115
|
+
|
|
116
|
+
Do not add `.fimo/config.json#routes` to a Next project yet. The current route-metadata validator still targets React Router and expects `src/routes.ts`; a first-class Next route catalog and preview bridge are separate product work. Fimo content, labels, locales, and hosting do not require that catalog.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Setup Fimo on React Router 8
|
|
2
|
+
|
|
3
|
+
This recipe describes the canonical Fimo + React Router framework-mode setup.
|
|
4
|
+
It mirrors what `fimo init` scaffolds today; you can also apply it to an
|
|
5
|
+
existing RR project.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- Node 22.22+
|
|
10
|
+
- A Fimo project created via `fimo init` (gives you `projectId`,
|
|
11
|
+
`organizationId`, and an `apiUrl`).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
pnpm add fimo react react-dom react-router @tanstack/react-query
|
|
17
|
+
pnpm add -D @react-router/dev @react-router/node @tailwindcss/vite tailwindcss vite @types/node @types/react @types/react-dom typescript
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## File: `package.json`
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"name": "__PROJECT_SLUG__",
|
|
25
|
+
"version": "0.0.0",
|
|
26
|
+
"private": true,
|
|
27
|
+
"type": "module",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "react-router dev --port 5173",
|
|
30
|
+
"build": "react-router build && fimo export-static",
|
|
31
|
+
"preview": "vite preview --host 0.0.0.0 --port 4173",
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## File: `vite.config.ts`
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { reactRouter } from '@react-router/dev/vite';
|
|
41
|
+
import { resolve } from 'path';
|
|
42
|
+
import { defineConfig } from 'vite';
|
|
43
|
+
|
|
44
|
+
import { fimo } from 'fimo/vite';
|
|
45
|
+
|
|
46
|
+
export default defineConfig({
|
|
47
|
+
plugins: [reactRouter(), fimo({ sitemap: true })],
|
|
48
|
+
resolve: {
|
|
49
|
+
alias: {
|
|
50
|
+
'@': resolve(__dirname, './src/'),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Notes:
|
|
57
|
+
|
|
58
|
+
- **You** call `reactRouter()`. `fimo/vite` no longer auto-injects it — the
|
|
59
|
+
user's vite.config owns the framework plugin. This is the library-mode shape.
|
|
60
|
+
- `fimo()` is synchronous; it returns a `PluginOption[]` you drop straight into
|
|
61
|
+
`plugins`. Don't `await` it or spread it.
|
|
62
|
+
- Do **not** add `fimo({ seo: true })` here. RR handles SEO via the `meta`
|
|
63
|
+
export (see `src/root.tsx`); the build-time SEO plugin would conflict.
|
|
64
|
+
- `sitemap` is opt-in and off by default; pass `fimo({ sitemap: true })` to emit
|
|
65
|
+
`sitemap.xml` (the canonical template does this). Omitting the option leaves
|
|
66
|
+
it off.
|
|
67
|
+
- `fimo({ robots: true })` opt-in to emit `robots.txt`.
|
|
68
|
+
|
|
69
|
+
## File: `react-router.config.ts`
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import type { Config } from '@react-router/dev/config';
|
|
73
|
+
|
|
74
|
+
export default {
|
|
75
|
+
appDirectory: 'src',
|
|
76
|
+
ssr: false,
|
|
77
|
+
// Static-site generation is opt-in. By default this template builds as an
|
|
78
|
+
// SPA (single index.html shell, client-side routing). To prerender routes at
|
|
79
|
+
// build time — required for crawler-visible per-route SEO — add this line:
|
|
80
|
+
//
|
|
81
|
+
// import { getFimoPaths } from 'fimo/paths';
|
|
82
|
+
// prerender: async () => (await getFimoPaths()).map((p) => p.path),
|
|
83
|
+
//
|
|
84
|
+
// `getFimoPaths()` expands dynamic routes (e.g. `/blog/:slug`) by fetching
|
|
85
|
+
// schema entries from the content API. See `references/frameworks/react-router.md`
|
|
86
|
+
// in the fimo skill for the full setup pattern.
|
|
87
|
+
buildDirectory: 'dist',
|
|
88
|
+
} satisfies Config;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**SSG is opt-in now.** Default = SPA (one HTML shell). Add `prerender`
|
|
92
|
+
yourself if you want crawler-visible per-route HTML. The previous
|
|
93
|
+
"prerender everything by default" behavior was a website-builder opinion
|
|
94
|
+
that doesn't fit infrastructure mode — your call which routes to prerender,
|
|
95
|
+
when to render them, and how.
|
|
96
|
+
|
|
97
|
+
## File: `src/root.tsx`
|
|
98
|
+
|
|
99
|
+
The explicit `QueryClientProvider` below is the canonical template shape so the
|
|
100
|
+
app can configure query defaults such as `staleTime`. Adopted SPAs can omit it:
|
|
101
|
+
`FimoProviders` reuses an ancestor client when present and supplies a stable
|
|
102
|
+
fallback otherwise.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
106
|
+
import { FimoProviders, FimoScripts, loadFimoMetaData, buildFimoMeta, fimoHtmlProps } from 'fimo/react-router';
|
|
107
|
+
import type { MetaFunction } from 'react-router';
|
|
108
|
+
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router';
|
|
109
|
+
|
|
110
|
+
const queryClient = new QueryClient({
|
|
111
|
+
defaultOptions: { queries: { staleTime: Infinity } },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export async function loader(args: { request: Request }) {
|
|
115
|
+
return loadFimoMetaData(args);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const meta: MetaFunction = (args) => buildFimoMeta(args);
|
|
119
|
+
|
|
120
|
+
export function Layout({ children }: { children: React.ReactNode }) {
|
|
121
|
+
return (
|
|
122
|
+
<html {...fimoHtmlProps()}>
|
|
123
|
+
<head>
|
|
124
|
+
<meta charSet="utf-8" />
|
|
125
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
126
|
+
<Meta />
|
|
127
|
+
<Links />
|
|
128
|
+
<FimoScripts />
|
|
129
|
+
</head>
|
|
130
|
+
<body>
|
|
131
|
+
{children}
|
|
132
|
+
<ScrollRestoration />
|
|
133
|
+
<Scripts />
|
|
134
|
+
</body>
|
|
135
|
+
</html>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default function AppRoot() {
|
|
140
|
+
return (
|
|
141
|
+
<QueryClientProvider client={queryClient}>
|
|
142
|
+
<FimoProviders>
|
|
143
|
+
<Outlet />
|
|
144
|
+
</FimoProviders>
|
|
145
|
+
</QueryClientProvider>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## File: `src/routes.ts`
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import type { RouteConfig } from '@react-router/dev/routes';
|
|
154
|
+
import { index } from '@react-router/dev/routes';
|
|
155
|
+
import { fimoRoutes } from 'fimo/react-router/routes';
|
|
156
|
+
|
|
157
|
+
const routes = [index('./pages/Index.tsx', { id: 'home' })] satisfies RouteConfig;
|
|
158
|
+
|
|
159
|
+
export default fimoRoutes(routes) satisfies RouteConfig;
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Page filenames are your choice; the scaffold uses PascalCase (e.g. `Index.tsx`,
|
|
163
|
+
`Privacy.tsx`).
|
|
164
|
+
|
|
165
|
+
`fimoRoutes()` is Fimo's React Router helper for path-prefix locales. It is a no-op for single-locale projects and expands concrete locale aliases when `.fimo/config.json#i18n.locales` contains more locales. Custom routers can ignore this helper and pass their own locale to `<FimoProviders locale={locale}>`; see `references/locales.md`.
|
|
166
|
+
|
|
167
|
+
### Locale-aware navigation
|
|
168
|
+
|
|
169
|
+
Use `FimoLink` for internal links that should preserve the current locale or deliberately switch it:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { FimoLink } from 'fimo/react-router';
|
|
173
|
+
|
|
174
|
+
<FimoLink to="/pricing">Pricing</FimoLink>
|
|
175
|
+
<FimoLink to="/pricing" locale="es">Spanish pricing</FimoLink>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Read the active locale with `useLocale()` when component logic needs the locale list, current canonical route, or document direction:
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import { useLocale } from 'fimo/ui';
|
|
182
|
+
|
|
183
|
+
const { locale, defaultLocale, locales, routePath, dir } = useLocale();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`FimoLink` localizes absolute internal paths using `.fimo/config.json#i18n.routing`; relative, hash, query-only, and external URLs remain unchanged. Copyable route and navigation examples live in `assets/react-router-locales/`.
|
|
187
|
+
|
|
188
|
+
## File: `src/pages/Index.tsx`
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { Text, useLabels } from 'fimo/ui';
|
|
192
|
+
|
|
193
|
+
export default function HomePage() {
|
|
194
|
+
const { t } = useLabels();
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<main>
|
|
198
|
+
<Text value={t('home.hero.title')} as="h1" />
|
|
199
|
+
</main>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## File: `.fimo/config.json`
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"version": "4",
|
|
209
|
+
"seo": {
|
|
210
|
+
"title": "__PROJECT_SLUG__",
|
|
211
|
+
"siteName": "__PROJECT_SLUG__"
|
|
212
|
+
},
|
|
213
|
+
"routes": {
|
|
214
|
+
"home": {
|
|
215
|
+
"label": "Home",
|
|
216
|
+
"path": "/"
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## File: `.fimo/project.json`
|
|
223
|
+
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"projectId": "__PROJECT_ID__",
|
|
227
|
+
"organizationId": "__ORG_ID__",
|
|
228
|
+
"apiUrl": "__API_URL__"
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Replace the `__PROJECT_ID__` / `__ORG_ID__` / `__API_URL__` sentinels with the
|
|
233
|
+
values from `fimo init`'s output (or `fimo projects info`).
|
|
234
|
+
|
|
235
|
+
## File: `tsconfig.json`
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"compilerOptions": {
|
|
240
|
+
"target": "ES2022",
|
|
241
|
+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
|
242
|
+
"module": "ESNext",
|
|
243
|
+
"moduleResolution": "Bundler",
|
|
244
|
+
"jsx": "react-jsx",
|
|
245
|
+
"strict": true,
|
|
246
|
+
"esModuleInterop": true,
|
|
247
|
+
"skipLibCheck": true,
|
|
248
|
+
"resolveJsonModule": true,
|
|
249
|
+
"isolatedModules": true,
|
|
250
|
+
"types": ["@react-router/node", "vite/client"],
|
|
251
|
+
"paths": { "@/*": ["./src/*"] }
|
|
252
|
+
},
|
|
253
|
+
"include": ["src", "*.config.ts"]
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## File: `index.css`
|
|
258
|
+
|
|
259
|
+
```css
|
|
260
|
+
@import 'tailwindcss';
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
(Optional — only if using Tailwind. Tailwind is not required for Fimo to
|
|
264
|
+
function; it just ships with the canonical template.)
|
|
265
|
+
|
|
266
|
+
## Verify and iterate
|
|
267
|
+
|
|
268
|
+
Run the app with the project's package-manager `dev` script (`pnpm dev` /
|
|
269
|
+
`npm run dev` / `yarn dev` / `bun dev` — match the lockfile). It serves the site
|
|
270
|
+
on localhost with live reload — this is your iteration loop: edit, see the change,
|
|
271
|
+
repeat with the developer.
|
|
272
|
+
|
|
273
|
+
`fimo deploy` is on-demand, not part of this loop: run it only when the developer
|
|
274
|
+
wants a hosted preview, a shareable link, or to go live — and only on their signal.
|
|
275
|
+
The dashboard's preview iframe shows the **deployed** app, so it only reflects
|
|
276
|
+
changes after a `fimo deploy` (the local dev server is localhost-only).
|
|
277
|
+
|
|
278
|
+
## What you don't add
|
|
279
|
+
|
|
280
|
+
- **No `fimo({ seo: true })`** — React Router's `meta` export covers SEO via
|
|
281
|
+
`loadFimoMetaData` + `buildFimoMeta`. The build-time SEO plugin is for
|
|
282
|
+
framework-less Vite + React setups (`references/frameworks/vite-react.md`).
|
|
283
|
+
- **No `<FimoSeo />` component** — not part of the RR setup.
|
|
284
|
+
- **No manual `<script src="overlay-runtime.js">`** — `<FimoScripts />`
|
|
285
|
+
handles preview-script + overlay loading.
|
|
286
|
+
|
|
287
|
+
## What to do next
|
|
288
|
+
|
|
289
|
+
- Add more routes: extend `src/routes.ts`, create matching `src/pages/*.tsx`,
|
|
290
|
+
add route entries in `.fimo/config.json#routes`.
|
|
291
|
+
- Add a sitemap: pass `fimo({ sitemap: true })` (opt-in, off by default; the
|
|
292
|
+
canonical template enables it). To customize the base URL, set
|
|
293
|
+
`.fimo/config.json#seo.url`.
|
|
294
|
+
- Override SEO per route with React Router's `meta` export and the existing `loadFimoMetaData` / `buildFimoMeta` integration in `src/root.tsx`.
|
|
295
|
+
- Look up rendering exports in `references/ui.md` and generated content APIs in `references/content/clients.md`.
|