fimo 2.4.0-staging.2 → 2.4.0-staging.4
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/skills/fimo-cli/SKILL.md +1 -1
- package/assets/skills/fimo-cli/references/branches.md +4 -4
- package/assets/skills/fimo-cli/references/deploy.md +2 -5
- package/assets/skills/fimo-cli/references/domains.md +5 -5
- package/assets/skills/fimo-cli/references/hosting.md +5 -4
- package/assets/skills/fimo-cli/references/preview.md +1 -1
- package/assets/skills/fimo-cli/references/projects.md +5 -5
- package/assets/skills/fimo-code/SKILL.md +1 -0
- package/assets/skills/fimo-code/references/assets.md +4 -2
- package/assets/skills/fimo-code/references/best-practices/next.md +0 -2
- package/assets/skills/fimo-code/references/content/schema.md +4 -1
- package/assets/skills/fimo-code/references/design.md +2 -2
- package/assets/skills/fimo-code/references/frameworks/astro.md +37 -1
- package/assets/skills/fimo-code/references/frameworks/compatibility.md +5 -5
- package/assets/skills/fimo-code/references/frameworks/hosting.md +23 -2
- package/assets/skills/fimo-code/references/frameworks/seo-head.md +78 -0
- package/assets/skills/fimo-code/references/frameworks/sveltekit.md +57 -4
- package/assets/skills/fimo-migration/SKILL.md +4 -0
- package/assets/skills/fimo-migration/references/qa.md +6 -0
- package/assets/skills/fimo-studio/references/billing-and-credits.md +7 -4
- package/assets/skills/fimo-studio/references/seo-settings.md +2 -0
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +11 -10
- package/dist/runtime/seo/head.d.ts +31 -0
- package/dist/runtime/seo/head.js +47 -0
- package/dist/runtime/seo/index.d.ts +1 -0
- package/dist/runtime/seo/index.js +1 -0
- package/dist/runtime/seo/resolver.d.ts +16 -2
- package/dist/scripts/sync-schemas.js +12 -5
- package/package.json +8 -8
- package/templates/astro/.fimo/config.json +1 -0
- package/templates/astro/AGENTS.md +1 -1
- package/templates/astro/astro.config.mjs +1 -0
- package/templates/astro/package.json +2 -2
- package/templates/astro/src/layouts/Base.astro +20 -4
- package/templates/astro/src/pages/[locale]/index.astro +1 -1
- package/templates/astro/src/pages/[locale]/projects/[slug].astro +4 -1
- package/templates/astro/src/pages/index.astro +1 -1
- package/templates/astro/src/pages/projects/[slug].astro +4 -1
- package/templates/nextjs/package.json +3 -3
- package/templates/react-router/package.json +4 -4
- package/templates/sveltekit/AGENTS.md +1 -1
- package/templates/sveltekit/package.json +2 -2
- package/templates/sveltekit/src/routes/+layout.server.ts +4 -0
- package/templates/sveltekit/src/routes/+layout.svelte +12 -0
- package/templates/sveltekit/src/routes/[[locale=locale]]/+page.svelte +0 -5
- package/templates/vite/package.json +4 -4
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FimoSEO } from '../shared/fimo-config.types.js';
|
|
2
|
+
export type FimoHeadMeta = {
|
|
3
|
+
name: string;
|
|
4
|
+
content: string;
|
|
5
|
+
} | {
|
|
6
|
+
property: string;
|
|
7
|
+
content: string;
|
|
8
|
+
};
|
|
9
|
+
export interface FimoHeadLink {
|
|
10
|
+
rel: string;
|
|
11
|
+
href: string;
|
|
12
|
+
}
|
|
13
|
+
/** Document head for one page: spread each entry onto a `<meta>` or `<link>`. */
|
|
14
|
+
export interface FimoHead {
|
|
15
|
+
title?: string;
|
|
16
|
+
meta: FimoHeadMeta[];
|
|
17
|
+
links: FimoHeadLink[];
|
|
18
|
+
}
|
|
19
|
+
export interface SeoHeadOptions {
|
|
20
|
+
/** Final canonical URL chosen by the app, also used for og:url. Omit to emit neither tag. */
|
|
21
|
+
canonicalUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Convert final SEO values to native tag attributes without I/O, route resolution
|
|
25
|
+
* or merging. The app resolves content and policy first. Absent/empty fields emit
|
|
26
|
+
* no tags; omitted SEO emits an empty head unless a canonical URL is supplied.
|
|
27
|
+
* `seo.url` is a site base, not a page canonical: only options.canonicalUrl emits
|
|
28
|
+
* canonical/og:url. Render attributes through the framework, never as raw HTML.
|
|
29
|
+
*/
|
|
30
|
+
export declare function toSeoHead(seo?: FimoSEO, options?: SeoHeadOptions): FimoHead;
|
|
31
|
+
//# sourceMappingURL=head.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert final SEO values to native tag attributes without I/O, route resolution
|
|
3
|
+
* or merging. The app resolves content and policy first. Absent/empty fields emit
|
|
4
|
+
* no tags; omitted SEO emits an empty head unless a canonical URL is supplied.
|
|
5
|
+
* `seo.url` is a site base, not a page canonical: only options.canonicalUrl emits
|
|
6
|
+
* canonical/og:url. Render attributes through the framework, never as raw HTML.
|
|
7
|
+
*/
|
|
8
|
+
export function toSeoHead(seo = {}, options = {}) {
|
|
9
|
+
const canonical = options.canonicalUrl;
|
|
10
|
+
if (!Object.values(seo).some(Boolean) && !canonical) {
|
|
11
|
+
return { meta: [], links: [] };
|
|
12
|
+
}
|
|
13
|
+
const meta = [];
|
|
14
|
+
const named = (name, content) => {
|
|
15
|
+
if (content) {
|
|
16
|
+
meta.push({ name, content });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const property = (name, content) => {
|
|
20
|
+
if (content) {
|
|
21
|
+
meta.push({ property: name, content });
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
named('description', seo.description);
|
|
25
|
+
named('robots', seo.robots);
|
|
26
|
+
named('theme-color', seo.themeColor);
|
|
27
|
+
property('og:type', 'website');
|
|
28
|
+
property('og:site_name', seo.siteName);
|
|
29
|
+
property('og:title', seo.title);
|
|
30
|
+
property('og:description', seo.description);
|
|
31
|
+
property('og:url', canonical);
|
|
32
|
+
property('og:image', seo.image);
|
|
33
|
+
property('og:locale', seo.locale);
|
|
34
|
+
named('twitter:card', 'summary_large_image');
|
|
35
|
+
named('twitter:site', seo.twitterSite);
|
|
36
|
+
named('twitter:title', seo.title);
|
|
37
|
+
named('twitter:description', seo.description);
|
|
38
|
+
named('twitter:image', seo.image);
|
|
39
|
+
const links = [];
|
|
40
|
+
if (canonical) {
|
|
41
|
+
links.push({ rel: 'canonical', href: canonical });
|
|
42
|
+
}
|
|
43
|
+
if (seo.favicon) {
|
|
44
|
+
links.push({ rel: 'icon', href: seo.favicon }, { rel: 'apple-touch-icon', href: seo.favicon });
|
|
45
|
+
}
|
|
46
|
+
return { ...(seo.title ? { title: seo.title } : {}), meta, links };
|
|
47
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FimoRouteConfig, FimoSEO, RouteSEO } from '../shared/fimo-config.types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Shared SEO resolver for the generated app.
|
|
4
4
|
*
|
|
@@ -40,5 +40,19 @@ export declare function matchRoutePath(pathname: string, pathPattern: string): {
|
|
|
40
40
|
export declare function resolveRouteConfig(pathname: string, routes: Record<string, FimoRouteConfig> | undefined): FimoRouteConfig | undefined;
|
|
41
41
|
export declare function getNestedValue(input: unknown, path: string): unknown;
|
|
42
42
|
export declare function resolveTemplate(template: string, context: Record<string, unknown>): string;
|
|
43
|
-
|
|
43
|
+
/** SEO slices of an imported JSON config; unrelated framework literal unions need not be preserved. */
|
|
44
|
+
export interface FimoSeoConfig {
|
|
45
|
+
seo?: FimoSEO;
|
|
46
|
+
i18n?: {
|
|
47
|
+
defaultLocale?: string;
|
|
48
|
+
locales?: string[];
|
|
49
|
+
};
|
|
50
|
+
routes?: Record<string, {
|
|
51
|
+
path?: string;
|
|
52
|
+
label?: string;
|
|
53
|
+
seo?: RouteSEO;
|
|
54
|
+
params?: object;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
|
57
|
+
export declare function resolveSeoForPath(pathname: string, config: FimoSeoConfig, routeData?: unknown): FimoSEO | undefined;
|
|
44
58
|
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -144,6 +144,7 @@ function resolveProjectRuntime(cwd) {
|
|
|
144
144
|
version,
|
|
145
145
|
providesLegacyReactRuntime: packageExportsSubpath(packageJson, './ui'),
|
|
146
146
|
providesReactRuntime: packageExportsSubpath(readProjectPackage(cwd, 'fimo-react'), '.'),
|
|
147
|
+
providesLegacyNextRuntime: packageExportsSubpath(packageJson, './next') && packageExportsSubpath(packageJson, './next/client'),
|
|
147
148
|
providesNextRuntime: packageExportsSubpath(readProjectPackage(cwd, 'fimo-next'), '.') &&
|
|
148
149
|
packageExportsSubpath(readProjectPackage(cwd, 'fimo-next'), './client'),
|
|
149
150
|
providesAstroRuntime: packageExportsSubpath(packageJson, './astro'),
|
|
@@ -154,7 +155,7 @@ function resolveProjectRuntime(cwd) {
|
|
|
154
155
|
}
|
|
155
156
|
function contentRuntimeImportFor(framework, runtime) {
|
|
156
157
|
if (framework === 'nextjs') {
|
|
157
|
-
return 'fimo-next';
|
|
158
|
+
return isVersionAtLeast(runtime.version, MINIMUM_FRAMEWORK_COMPANION_RUNTIME_VERSION) ? 'fimo-next' : 'fimo/next';
|
|
158
159
|
}
|
|
159
160
|
if (framework === 'astro') {
|
|
160
161
|
return 'fimo/astro';
|
|
@@ -196,6 +197,12 @@ function resolveSchemaGenerationTarget(runtimeImport, runtime) {
|
|
|
196
197
|
if (runtimeImport === 'fimo-react') {
|
|
197
198
|
return 'react';
|
|
198
199
|
}
|
|
200
|
+
if (runtimeImport === 'fimo/next' && !runtime.providesLegacyNextRuntime) {
|
|
201
|
+
throw new Error(`Cannot generate schema clients because project-local fimo@${runtime.version} does not provide the ` +
|
|
202
|
+
'legacy fimo/next and fimo/next/client exports required by this Next.js project. ' +
|
|
203
|
+
`Run \`${CONTENT_CLIENT_RUNTIME_UPGRADE_COMMAND}\` in the project, then run \`npx fimo@latest validate\` again. ` +
|
|
204
|
+
'No generated files were changed.');
|
|
205
|
+
}
|
|
199
206
|
if (runtimeImport === 'fimo-next' && !runtime.providesNextRuntime) {
|
|
200
207
|
throw new Error(`Cannot generate schema clients because the project does not provide the ` +
|
|
201
208
|
'fimo-next and fimo-next/client exports required by this Next.js project. ' +
|
|
@@ -213,7 +220,7 @@ function resolveSchemaGenerationTarget(runtimeImport, runtime) {
|
|
|
213
220
|
`Run \`npm install fimo-sveltekit@${runtime.version}\` in the project, then run \`npx fimo@latest validate\` again. ` +
|
|
214
221
|
'No generated files were changed.');
|
|
215
222
|
}
|
|
216
|
-
if (runtimeImport === 'fimo-next') {
|
|
223
|
+
if (runtimeImport === 'fimo/next' || runtimeImport === 'fimo-next') {
|
|
217
224
|
return 'next';
|
|
218
225
|
}
|
|
219
226
|
return runtimeImport === 'fimo/astro' ? 'astro' : 'sveltekit';
|
|
@@ -224,17 +231,17 @@ function templatesForTarget(target, runtime, runtimeImport) {
|
|
|
224
231
|
{
|
|
225
232
|
suffix: '.types.ts',
|
|
226
233
|
path: templatePath(runtime, 'types'),
|
|
227
|
-
runtimeImport
|
|
234
|
+
runtimeImport,
|
|
228
235
|
},
|
|
229
236
|
{
|
|
230
237
|
suffix: '.ts',
|
|
231
238
|
path: templatePath(runtime, 'nextServer'),
|
|
232
|
-
runtimeImport
|
|
239
|
+
runtimeImport,
|
|
233
240
|
},
|
|
234
241
|
{
|
|
235
242
|
suffix: '.client.ts',
|
|
236
243
|
path: templatePath(runtime, 'nextClient'),
|
|
237
|
-
runtimeImport:
|
|
244
|
+
runtimeImport: `${runtimeImport}/client`,
|
|
238
245
|
},
|
|
239
246
|
];
|
|
240
247
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fimo",
|
|
3
|
-
"version": "2.4.0-staging.
|
|
3
|
+
"version": "2.4.0-staging.4",
|
|
4
4
|
"description": "Fimo CLI - create, deploy, and manage Fimo projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro-component"
|
|
@@ -193,13 +193,13 @@
|
|
|
193
193
|
"devDependencies": {
|
|
194
194
|
"@clack/core": "^1.2.0",
|
|
195
195
|
"@clack/prompts": "^1.2.0",
|
|
196
|
-
"@fimo/agents-bundle": "2.4.0-staging.
|
|
197
|
-
"@fimo/api-contracts": "2.4.0-staging.
|
|
198
|
-
"@fimo/cli-kit": "2.4.0-staging.
|
|
199
|
-
"@fimo/framework-detect": "2.4.0-staging.
|
|
200
|
-
"@fimo/routes": "2.4.0-staging.
|
|
201
|
-
"@fimo/sdk": "2.4.0-staging.
|
|
202
|
-
"@fimo/tsconfig": "2.4.0-staging.
|
|
196
|
+
"@fimo/agents-bundle": "2.4.0-staging.4",
|
|
197
|
+
"@fimo/api-contracts": "2.4.0-staging.4",
|
|
198
|
+
"@fimo/cli-kit": "2.4.0-staging.4",
|
|
199
|
+
"@fimo/framework-detect": "2.4.0-staging.4",
|
|
200
|
+
"@fimo/routes": "2.4.0-staging.4",
|
|
201
|
+
"@fimo/sdk": "2.4.0-staging.4",
|
|
202
|
+
"@fimo/tsconfig": "2.4.0-staging.4",
|
|
203
203
|
"@types/node": "22.18.6",
|
|
204
204
|
"agent-install": "^0.0.5",
|
|
205
205
|
"astro": "^7.1.6",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"project": {
|
|
19
19
|
"label": "Project",
|
|
20
20
|
"path": "/:locale/projects/:slug",
|
|
21
|
+
"seo": { "title": "{{project.title}}", "description": "{{project.summary}}" },
|
|
21
22
|
"params": {
|
|
22
23
|
"locale": { "type": "locale", "optional": true },
|
|
23
24
|
"slug": { "schema": "Project", "queryField": "slug", "displayField": "title" }
|
|
@@ -31,7 +31,7 @@ a code-only task, or use `npx fimo@latest` for project-local code generation and
|
|
|
31
31
|
- Treat the user's language request as part of setup. The first explicitly requested locale is the primary locale. Load `fimo skills get fimo-cli --ref locales` and use `fimo locales` to configure the exact locale set and default. Never edit `i18n.defaultLocale` or `i18n.locales` directly. If no language is requested, keep `en` and state that choice. Astro reads the synchronized config, keeps the default locale unprefixed, and prefixes the others.
|
|
32
32
|
- Preserve both the unprefixed default pages and the `[locale]` page tree. Astro has no optional single-segment file route, so the paired native pages keep one-locale URLs clean and allow another locale without changing the route shape. Use `Astro.currentLocale` for Fimo content reads, `getLabels`, and document language.
|
|
33
33
|
- Keep `.fimo/config.json#routes` as Studio metadata aligned with Astro's page files. It does not configure Astro routing.
|
|
34
|
-
- Astro owns routes, locale resolution, links
|
|
34
|
+
- Astro owns routes, locale resolution, links and SEO. Base.astro resolves the actual path with `resolveSeoForPath` and renders `toSeoHead` from `fimo/seo`. Content pages pass already-resolved `seo` with their route data. Keep title/social tags in agreement and preserve the app's canonical policy.
|
|
35
35
|
- Use generated schema clients, `getLabels({ locale })`, and native components from `fimo/astro/components` for editable values.
|
|
36
36
|
- Do not add React or a second deployment platform.
|
|
37
37
|
- Never edit `.fimo/project.json`; it links this directory to the remote project.
|
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { resolveSeoForPath, toSeoHead, type FimoSEO } from 'fimo/seo';
|
|
3
|
+
|
|
4
|
+
import fimoConfig from '../../.fimo/config.json';
|
|
5
|
+
|
|
2
6
|
interface Props {
|
|
3
7
|
lang: string;
|
|
4
|
-
title
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
seo?: FimoSEO;
|
|
11
|
+
canonicalUrl?: string | null;
|
|
5
12
|
}
|
|
6
13
|
|
|
7
|
-
const { lang, title } = Astro.props;
|
|
14
|
+
const { lang, title, description, seo = resolveSeoForPath(Astro.url.pathname, fimoConfig) } = Astro.props;
|
|
15
|
+
const canonicalUrl = Astro.props.canonicalUrl === undefined
|
|
16
|
+
? (seo?.url ? new URL(Astro.url.pathname, seo.url).href : undefined)
|
|
17
|
+
: Astro.props.canonicalUrl ?? undefined;
|
|
18
|
+
const head = toSeoHead({
|
|
19
|
+
...seo,
|
|
20
|
+
...(title !== undefined ? { title } : {}),
|
|
21
|
+
...(description !== undefined ? { description } : {}),
|
|
22
|
+
}, { canonicalUrl });
|
|
8
23
|
---
|
|
9
24
|
|
|
10
25
|
<!doctype html>
|
|
@@ -12,10 +27,11 @@ const { lang, title } = Astro.props;
|
|
|
12
27
|
<head>
|
|
13
28
|
<meta charset="UTF-8" />
|
|
14
29
|
<meta name="viewport" content="width=device-width" />
|
|
15
|
-
<
|
|
30
|
+
<title>{head.title ?? 'Fimo + Astro'}</title>
|
|
31
|
+
{head.meta.map((attributes) => <meta {...attributes} />)}
|
|
32
|
+
{head.links.map((attributes) => <link {...attributes} />)}
|
|
16
33
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
17
34
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
18
|
-
<title>{title}</title>
|
|
19
35
|
</head>
|
|
20
36
|
<body>
|
|
21
37
|
<slot />
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getLabels } from 'fimo/astro';
|
|
3
3
|
import { Text } from 'fimo/astro/components';
|
|
4
|
+
import { resolveSeoForPath } from 'fimo/seo';
|
|
4
5
|
|
|
6
|
+
import config from '../../../../.fimo/config.json';
|
|
5
7
|
import Base from '../../../layouts/Base.astro';
|
|
6
8
|
import { prefixedLocales } from '../../../locales';
|
|
7
9
|
import * as Project from '../../../schemas/Project';
|
|
@@ -22,9 +24,10 @@ const [project, labels] = await Promise.all([Project.getBySlug(slug, { locale })
|
|
|
22
24
|
if (!project) {
|
|
23
25
|
return Astro.redirect('/404');
|
|
24
26
|
}
|
|
27
|
+
const seo = resolveSeoForPath(Astro.url.pathname, config, { project });
|
|
25
28
|
---
|
|
26
29
|
|
|
27
|
-
<Base lang={locale} title={String(project.title)}>
|
|
30
|
+
<Base lang={locale} seo={seo} title={seo?.title ?? String(project.title)}>
|
|
28
31
|
<main data-framework="astro" data-rendering="server" data-project-slug={slug}>
|
|
29
32
|
<a href={`/${locale}/projects`}><Text as="span" value={labels.t('navigation.projects')} /></a>
|
|
30
33
|
<article>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getLabels } from 'fimo/astro';
|
|
3
3
|
import { Text } from 'fimo/astro/components';
|
|
4
|
+
import { resolveSeoForPath } from 'fimo/seo';
|
|
4
5
|
|
|
6
|
+
import config from '../../../.fimo/config.json';
|
|
5
7
|
import Base from '../../layouts/Base.astro';
|
|
6
8
|
import { defaultLocale } from '../../locales';
|
|
7
9
|
import * as Project from '../../schemas/Project';
|
|
@@ -15,9 +17,10 @@ const [project, labels] = await Promise.all([Project.getBySlug(slug, { locale })
|
|
|
15
17
|
if (!project) {
|
|
16
18
|
return Astro.redirect('/404');
|
|
17
19
|
}
|
|
20
|
+
const seo = resolveSeoForPath(Astro.url.pathname, config, { project });
|
|
18
21
|
---
|
|
19
22
|
|
|
20
|
-
<Base lang={locale} title={String(project.title)}>
|
|
23
|
+
<Base lang={locale} seo={seo} title={seo?.title ?? String(project.title)}>
|
|
21
24
|
<main data-framework="astro" data-rendering="server" data-project-slug={slug}>
|
|
22
25
|
<a href="/projects"><Text as="span" value={labels.t('navigation.projects')} /></a>
|
|
23
26
|
<article>
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@tanstack/react-query": "^5.80.6",
|
|
15
|
-
"fimo": "2.4.0-staging.
|
|
16
|
-
"fimo-next": "2.4.0-staging.
|
|
17
|
-
"fimo-react": "2.4.0-staging.
|
|
15
|
+
"fimo": "2.4.0-staging.4",
|
|
16
|
+
"fimo-next": "2.4.0-staging.4",
|
|
17
|
+
"fimo-react": "2.4.0-staging.4",
|
|
18
18
|
"next": "^16.3.3",
|
|
19
19
|
"react": "^19.2.7",
|
|
20
20
|
"react-dom": "^19.2.7"
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"cn": "0.2.4",
|
|
22
22
|
"date-fns": "^4.1.0",
|
|
23
23
|
"embla-carousel-react": "^8.6.0",
|
|
24
|
-
"fimo": "2.4.0-staging.
|
|
25
|
-
"fimo-react": "2.4.0-staging.
|
|
26
|
-
"fimo-react-router": "2.4.0-staging.
|
|
27
|
-
"fimo-vite": "2.4.0-staging.
|
|
24
|
+
"fimo": "2.4.0-staging.4",
|
|
25
|
+
"fimo-react": "2.4.0-staging.4",
|
|
26
|
+
"fimo-react-router": "2.4.0-staging.4",
|
|
27
|
+
"fimo-vite": "2.4.0-staging.4",
|
|
28
28
|
"input-otp": "^1.4.2",
|
|
29
29
|
"isbot": "^5",
|
|
30
30
|
"lucide-react": "^0.577.0",
|
|
@@ -32,7 +32,7 @@ a code-only task, or use `npx fimo@latest` for project-local code generation and
|
|
|
32
32
|
- Preserve the native `[[locale=locale]]` route boundary. The optional parameter keeps the default locale at `/`; the matcher prevents ordinary top-level routes from being mistaken for locales. The root server layout passes the tracked `event.params` through `resolveLocale`, then sends that locale to `getLabels` and `FimoProvider`. Remove the extra brackets only when every locale must be prefixed.
|
|
33
33
|
- Keep the document language request-aware: `src/hooks.server.ts` renders the initial `<html lang>`, and the root layout updates it after client navigation.
|
|
34
34
|
- Keep `.fimo/config.json#routes` as Studio metadata aligned with SvelteKit's route files. It does not configure SvelteKit routing.
|
|
35
|
-
- The application owns routes, locale resolution, links
|
|
35
|
+
- The application owns routes, locale resolution, links and SEO. Return final `seoHead` from route loads using `resolveSeoForPath` and `toSeoHead` from `fimo/seo`. The root renders `page.data.seoHead` once. Content pages supply route data; do not add competing page head tags. Check client navigation for stale tags.
|
|
36
36
|
- Do not add a second deployment platform.
|
|
37
37
|
- Never edit `.fimo/project.json`; it links this directory to the remote project.
|
|
38
38
|
- Keep `.fimo/config.json#runtime` true to the code: framework, `dev`/`build`/`install` commands, output directory. Update it when you rename a package script, move the build output, or change framework; the reference is `fimo skills get fimo-code --ref frameworks/hosting`.
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"prepare": "svelte-kit sync"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"fimo": "2.4.0-staging.
|
|
15
|
-
"fimo-sveltekit": "2.4.0-staging.
|
|
14
|
+
"fimo": "2.4.0-staging.4",
|
|
15
|
+
"fimo-sveltekit": "2.4.0-staging.4"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@sveltejs/kit": "^2.70.2",
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { getLabels } from 'fimo-sveltekit';
|
|
2
|
+
import { resolveSeoForPath, toSeoHead } from 'fimo/seo';
|
|
2
3
|
|
|
4
|
+
import fimoConfig from '../../.fimo/config.json';
|
|
3
5
|
import { resolveLocale } from '../locales';
|
|
4
6
|
|
|
5
7
|
export async function load(event) {
|
|
6
8
|
const locale = resolveLocale(event.params);
|
|
9
|
+
const seo = resolveSeoForPath(event.url.pathname, fimoConfig);
|
|
7
10
|
|
|
8
11
|
return {
|
|
9
12
|
locale,
|
|
13
|
+
seoHead: toSeoHead(seo, { canonicalUrl: seo?.url ? new URL(event.url.pathname, seo.url).href : undefined }),
|
|
10
14
|
labels: await getLabels({ event, locale }),
|
|
11
15
|
};
|
|
12
16
|
}
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { afterNavigate } from '$app/navigation';
|
|
3
|
+
import { page } from '$app/state';
|
|
3
4
|
import { FimoProvider } from 'fimo-sveltekit/components';
|
|
4
5
|
import type { Snippet } from 'svelte';
|
|
5
6
|
|
|
6
7
|
import type { LayoutData } from './$types';
|
|
7
8
|
|
|
8
9
|
let { data, children }: { data: LayoutData; children: Snippet } = $props();
|
|
10
|
+
const head = $derived(page.data.seoHead ?? { meta: [], links: [] });
|
|
9
11
|
|
|
10
12
|
afterNavigate(() => {
|
|
11
13
|
document.documentElement.lang = data.locale;
|
|
12
14
|
});
|
|
13
15
|
</script>
|
|
14
16
|
|
|
17
|
+
<svelte:head>
|
|
18
|
+
<title>{head.title ?? 'Fimo + SvelteKit'}</title>
|
|
19
|
+
{#each head.meta as attributes}
|
|
20
|
+
<meta {...attributes} />
|
|
21
|
+
{/each}
|
|
22
|
+
{#each head.links as attributes}
|
|
23
|
+
<link {...attributes} />
|
|
24
|
+
{/each}
|
|
25
|
+
</svelte:head>
|
|
26
|
+
|
|
15
27
|
<FimoProvider labels={data.labels}>
|
|
16
28
|
{@render children()}
|
|
17
29
|
</FimoProvider>
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@tanstack/react-query": "^5.80.6",
|
|
14
|
-
"fimo": "2.4.0-staging.
|
|
15
|
-
"fimo-react": "2.4.0-staging.
|
|
16
|
-
"fimo-react-router": "2.4.0-staging.
|
|
17
|
-
"fimo-vite": "2.4.0-staging.
|
|
14
|
+
"fimo": "2.4.0-staging.4",
|
|
15
|
+
"fimo-react": "2.4.0-staging.4",
|
|
16
|
+
"fimo-react-router": "2.4.0-staging.4",
|
|
17
|
+
"fimo-vite": "2.4.0-staging.4",
|
|
18
18
|
"react": "^19.2.7",
|
|
19
19
|
"react-dom": "^19.2.7",
|
|
20
20
|
"react-router": "^8.3.0"
|