@decocms/start 1.4.1 → 1.4.2
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/package.json +1 -1
- package/src/sdk/workerEntry.ts +114 -6
package/package.json
CHANGED
package/src/sdk/workerEntry.ts
CHANGED
|
@@ -38,9 +38,10 @@ import { isMobileUA } from "./useDevice";
|
|
|
38
38
|
import { getRenderShellConfig } from "../admin/setup";
|
|
39
39
|
import { RequestContext } from "./requestContext";
|
|
40
40
|
import { getAppMiddleware } from "./setupApps";
|
|
41
|
-
import type { MatcherContext } from "../cms/resolve";
|
|
42
|
-
import { resolveDecoPage } from "../cms/resolve";
|
|
43
|
-
import { runSectionLoaders } from "../cms/sectionLoaders";
|
|
41
|
+
import type { MatcherContext, ResolvedSection } from "../cms/resolve";
|
|
42
|
+
import { resolveDecoPage, extractSeoFromProps, extractSeoFromSections } from "../cms/resolve";
|
|
43
|
+
import { runSectionLoaders, runSingleSectionLoader } from "../cms/sectionLoaders";
|
|
44
|
+
import { getSiteSeo } from "../cms/loader";
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* Append Link preload headers for CSS and fonts so the browser starts
|
|
@@ -978,10 +979,45 @@ export function createDecoWorkerEntry(
|
|
|
978
979
|
return Response.json(null, { status: 404, headers: { "Access-Control-Allow-Origin": "*" } });
|
|
979
980
|
}
|
|
980
981
|
const enrichedSections = await runSectionLoaders(page.resolvedSections, request);
|
|
981
|
-
|
|
982
|
+
|
|
983
|
+
// Build SEO props — same logic as buildPageSeo in cmsRoute.ts:
|
|
984
|
+
// 1. Run section loader on seoSection (e.g. SEOPDP)
|
|
985
|
+
// 2. Extract SEO fields from resolved props
|
|
986
|
+
// 3. Merge with site-wide SEO defaults from the "Site" app block
|
|
987
|
+
// 4. Extract section-contributed SEO from enriched sections
|
|
988
|
+
const seoProps = await buildAsJsonSeo(page.seoSection, enrichedSections, request);
|
|
989
|
+
const seoComponent = page.seoSection?.component ?? "website/sections/Seo/SeoV2.tsx";
|
|
990
|
+
|
|
991
|
+
// Build legacy deco-cx/deco (Fresh) compatible response shape.
|
|
992
|
+
// The old framework returns { props: { name, path, seo, sections, ... }, metadata }.
|
|
993
|
+
// Mobile apps and other consumers rely on this exact structure.
|
|
994
|
+
const sections = enrichedSections.map((s) => ({
|
|
995
|
+
props: s.props,
|
|
996
|
+
metadata: {
|
|
997
|
+
resolveChain: [],
|
|
998
|
+
component: s.component,
|
|
999
|
+
},
|
|
1000
|
+
}));
|
|
1001
|
+
|
|
982
1002
|
const result = {
|
|
983
|
-
|
|
984
|
-
|
|
1003
|
+
props: {
|
|
1004
|
+
name: page.name,
|
|
1005
|
+
path: page.path,
|
|
1006
|
+
seo: {
|
|
1007
|
+
props: seoProps,
|
|
1008
|
+
metadata: {
|
|
1009
|
+
resolveChain: [],
|
|
1010
|
+
component: seoComponent,
|
|
1011
|
+
},
|
|
1012
|
+
},
|
|
1013
|
+
sections,
|
|
1014
|
+
devMode: false,
|
|
1015
|
+
unindexedDomain: false,
|
|
1016
|
+
},
|
|
1017
|
+
metadata: {
|
|
1018
|
+
resolveChain: [],
|
|
1019
|
+
component: "website/pages/Page.tsx",
|
|
1020
|
+
},
|
|
985
1021
|
};
|
|
986
1022
|
return Response.json(result, {
|
|
987
1023
|
headers: {
|
|
@@ -1418,3 +1454,75 @@ export function createDecoWorkerEntry(
|
|
|
1418
1454
|
return dressResponse(origin, "MISS");
|
|
1419
1455
|
}
|
|
1420
1456
|
}
|
|
1457
|
+
|
|
1458
|
+
// ---------------------------------------------------------------------------
|
|
1459
|
+
// ?asJson SEO builder — mirrors buildPageSeo() from cmsRoute.ts
|
|
1460
|
+
// ---------------------------------------------------------------------------
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* Build SEO props for the ?asJson response, matching the legacy deco-cx/deco
|
|
1464
|
+
* format. Runs the SEO section loader, merges with site-wide SEO defaults,
|
|
1465
|
+
* and extracts section-contributed SEO.
|
|
1466
|
+
*/
|
|
1467
|
+
async function buildAsJsonSeo(
|
|
1468
|
+
seoSection: ResolvedSection | null | undefined,
|
|
1469
|
+
enrichedSections: ResolvedSection[],
|
|
1470
|
+
request: Request,
|
|
1471
|
+
): Promise<Record<string, unknown>> {
|
|
1472
|
+
const siteSeo = getSiteSeo();
|
|
1473
|
+
const sectionSeo = extractSeoFromSections(enrichedSections);
|
|
1474
|
+
|
|
1475
|
+
if (!seoSection) {
|
|
1476
|
+
const merged: Record<string, unknown> = { ...sectionSeo };
|
|
1477
|
+
if (siteSeo.title && !merged.title) merged.title = siteSeo.title;
|
|
1478
|
+
if (siteSeo.description && !merged.description) merged.description = siteSeo.description;
|
|
1479
|
+
if (siteSeo.image && !merged.image) merged.image = siteSeo.image;
|
|
1480
|
+
if (siteSeo.favicon) merged.favicon = siteSeo.favicon;
|
|
1481
|
+
if (!merged.jsonLDs) merged.jsonLDs = [];
|
|
1482
|
+
return merged;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// Run the section loader if registered (e.g. SEOPDP)
|
|
1486
|
+
let enrichedProps = seoSection.props;
|
|
1487
|
+
try {
|
|
1488
|
+
const enriched = await runSingleSectionLoader(seoSection, request);
|
|
1489
|
+
if (enriched) enrichedProps = enriched.props;
|
|
1490
|
+
} catch {
|
|
1491
|
+
// Section loader failed — use raw resolved props
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const pageSeo = extractSeoFromProps(enrichedProps);
|
|
1495
|
+
|
|
1496
|
+
// Merge site-wide SEO defaults (same as cmsRoute.ts buildPageSeo)
|
|
1497
|
+
if (!pageSeo.title && siteSeo.title) pageSeo.title = siteSeo.title;
|
|
1498
|
+
if (!pageSeo.description && siteSeo.description) pageSeo.description = siteSeo.description;
|
|
1499
|
+
if (!pageSeo.image && siteSeo.image) pageSeo.image = siteSeo.image;
|
|
1500
|
+
|
|
1501
|
+
// Apply title/description templates (mirrors buildPageSeo in cmsRoute.ts).
|
|
1502
|
+
// Priority: page-level template → site-level template → no-op.
|
|
1503
|
+
const rawProps = seoSection.props;
|
|
1504
|
+
const titleTemplate =
|
|
1505
|
+
effectiveTemplate(rawProps.titleTemplate as string | undefined) ??
|
|
1506
|
+
effectiveTemplate(siteSeo.titleTemplate);
|
|
1507
|
+
const descTemplate =
|
|
1508
|
+
effectiveTemplate(rawProps.descriptionTemplate as string | undefined) ??
|
|
1509
|
+
effectiveTemplate(siteSeo.descriptionTemplate);
|
|
1510
|
+
|
|
1511
|
+
if (titleTemplate && pageSeo.title) {
|
|
1512
|
+
pageSeo.title = titleTemplate.replace("%s", pageSeo.title);
|
|
1513
|
+
}
|
|
1514
|
+
if (descTemplate && pageSeo.description) {
|
|
1515
|
+
pageSeo.description = descTemplate.replace("%s", pageSeo.description);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
const merged = { ...sectionSeo, ...pageSeo } as Record<string, unknown>;
|
|
1519
|
+
if (siteSeo.favicon) merged.favicon = siteSeo.favicon;
|
|
1520
|
+
if (!merged.jsonLDs) merged.jsonLDs = [];
|
|
1521
|
+
return merged;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/** Returns a non-trivial template string, or undefined for "%s" / empty / blank. */
|
|
1525
|
+
function effectiveTemplate(tmpl: string | undefined): string | undefined {
|
|
1526
|
+
if (!tmpl || tmpl.trim() === "" || tmpl.trim() === "%s") return undefined;
|
|
1527
|
+
return tmpl;
|
|
1528
|
+
}
|