@dreamtree-org/landing-kit 0.6.0 → 0.7.0
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/MIGRATION.md +12 -0
- package/README.md +2 -1
- package/dist/server.cjs +33 -17
- package/dist/server.js +32 -17
- package/package.json +1 -1
- package/server.d.ts +9 -1
package/MIGRATION.md
CHANGED
|
@@ -106,3 +106,15 @@ Nothing changes for product landing pages. New, opt-in:
|
|
|
106
106
|
- Apps that stored per-tenant brand colours elsewhere can move them into a theme with
|
|
107
107
|
`validateTheme({ preset: 'custom', primary, accent })`.
|
|
108
108
|
|
|
109
|
+
|
|
110
|
+
## 0.7.0 — serve-time page transforms (additive)
|
|
111
|
+
|
|
112
|
+
Nothing changes unless you opt in:
|
|
113
|
+
|
|
114
|
+
- `landingRoutes(app, { transformPage })`: `(body, { req, route, file, kind }) => string | null`
|
|
115
|
+
(may be async) runs on every prerendered page (`kind: 'page'`) and crawler file (`'crawler'`)
|
|
116
|
+
before the origin is filled in. Return the rewritten body, or null to serve the prerendered one;
|
|
117
|
+
a throw is logged and also serves the prerendered body. It runs per request, so cache inside it.
|
|
118
|
+
- `fillPage(template, page)` from `/server`: the template fill `prerenderPages` does, as a string.
|
|
119
|
+
Keep a copy of the template (prerenderPages overwrites `landing.html`) and an SSR bundle to
|
|
120
|
+
re-render pages with live data, e.g. CMS-driven pricing.
|
package/README.md
CHANGED
|
@@ -266,7 +266,8 @@ to emit a graph without URLs. No ratings, reviews or prices are ever generated;
|
|
|
266
266
|
|
|
267
267
|
| Function | Meaning |
|
|
268
268
|
|---|---|
|
|
269
|
-
| `landingRoutes(app, { dist, isPublicHost, hasSession, redirects, onSession, onPrivateHost, trailingSlash, rawFiles, manifest, cacheControl })` | serves `/` (visitors only, `Vary: Cookie`), every page in the manifest (default `landing-pages.json`; its paths are relative to its own directory), robots/sitemap/llms with the origin filled in, verification meta tags from env; 404s the raw files (`rawFiles: 'redirect'` 301s them to their page instead); `/x/` → 301 `/x` (`trailingSlash: 'serve'` answers both); `isPublicHost` may be async; a non-public host gets `Disallow: /` and, at `/`, `next()` or the app shell (`onPrivateHost: 'app-shell'`) |
|
|
269
|
+
| `landingRoutes(app, { dist, isPublicHost, hasSession, redirects, onSession, onPrivateHost, trailingSlash, rawFiles, manifest, cacheControl, transformPage })` | serves `/` (visitors only, `Vary: Cookie`), every page in the manifest (default `landing-pages.json`; its paths are relative to its own directory), robots/sitemap/llms with the origin filled in, verification meta tags from env; 404s the raw files (`rawFiles: 'redirect'` 301s them to their page instead); `/x/` → 301 `/x` (`trailingSlash: 'serve'` answers both); `isPublicHost` may be async; a non-public host gets `Disallow: /` and, at `/`, `next()` or the app shell (`onPrivateHost: 'app-shell'`); `transformPage(body, { req, route, file, kind })` may rewrite a page or crawler file per request (e.g. live prices) and falls back to the prerendered body on null or a throw |
|
|
270
|
+
| `fillPage(template, page)` | one page poured into the template, as a string: what `prerenderPages` writes. Pair it with `transformPage` to re-render a page with live data |
|
|
270
271
|
| `prerenderPages({ dist, pages, files, homeFile, pagesDir, manifest })` | build step: fills `%TITLE% %DESCRIPTION% %PATH% %JSONLD% %HEAD% <!--landing-->` per page, writes `landing.html`, `landing-pages/…`, the manifest and crawler files; throws on an unfilled slot before writing anything |
|
|
271
272
|
| `sessionCheck({ cookie: 'JWT_TOKEN', bearer, verify })` | `hasSession(req)` from your own JWT check |
|
|
272
273
|
| `siteUrl(req)` · `verificationTags(env)` · `readCookie` · `fillSite` | helpers |
|
package/dist/server.cjs
CHANGED
|
@@ -43,6 +43,7 @@ __export(server_exports, {
|
|
|
43
43
|
buildSitemap: () => buildSitemap,
|
|
44
44
|
contentFacts: () => contentFacts,
|
|
45
45
|
escapeHtml: () => escapeHtml,
|
|
46
|
+
fillPage: () => fillPage,
|
|
46
47
|
fillSite: () => fillSite,
|
|
47
48
|
jsonLdScript: () => jsonLdScript,
|
|
48
49
|
landingRoutes: () => landingRoutes,
|
|
@@ -420,7 +421,8 @@ function landingRoutes(app, options = {}) {
|
|
|
420
421
|
crawlerCache = "public, max-age=3600",
|
|
421
422
|
privateRobots = PRIVATE_ROBOTS,
|
|
422
423
|
appUrl,
|
|
423
|
-
env = process.env
|
|
424
|
+
env = process.env,
|
|
425
|
+
transformPage
|
|
424
426
|
} = opts;
|
|
425
427
|
if (!app || typeof app.get !== "function") throw new Error("landing-kit landingRoutes: pass the Express app as the first argument.");
|
|
426
428
|
if (!dist) throw new Error('landing-kit landingRoutes: "dist" is missing \u2014 pass { dist: path.join(__dirname, "client/dist") }.');
|
|
@@ -452,9 +454,20 @@ function landingRoutes(app, options = {}) {
|
|
|
452
454
|
res.set("Cache-Control", "no-cache");
|
|
453
455
|
return res.sendFile(import_node_path.default.join(dist, "index.html"), (err) => err && next(err));
|
|
454
456
|
};
|
|
455
|
-
const
|
|
457
|
+
const bodyFor = async (req, body, meta) => {
|
|
458
|
+
if (!transformPage) return body;
|
|
459
|
+
try {
|
|
460
|
+
const out = await transformPage(body, { req, ...meta });
|
|
461
|
+
return typeof out === "string" ? out : body;
|
|
462
|
+
} catch (err) {
|
|
463
|
+
console.error(`landing-kit transformPage failed on ${meta.route}:`, err?.message || err);
|
|
464
|
+
return body;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
const sendPage = async (req, res, file, route) => {
|
|
468
|
+
const body = await bodyFor(req, read(file), { route, file, kind: "page" });
|
|
456
469
|
res.set("Cache-Control", cacheControl);
|
|
457
|
-
res.type("html").send(fillSite(
|
|
470
|
+
res.type("html").send(fillSite(body, origin(req), { html: true, env }));
|
|
458
471
|
};
|
|
459
472
|
app.get("/", guard(async (req, res, next) => {
|
|
460
473
|
const file = routes["/"];
|
|
@@ -462,7 +475,7 @@ function landingRoutes(app, options = {}) {
|
|
|
462
475
|
if (!await isPublic(req)) return onPrivateHost === "app-shell" ? appShell(res, next) : next();
|
|
463
476
|
res.set("Vary", "Cookie");
|
|
464
477
|
if (hasSession(req)) return onSession === "app-shell" ? appShell(res, next) : next();
|
|
465
|
-
sendPage(req, res, file);
|
|
478
|
+
await sendPage(req, res, file, "/");
|
|
466
479
|
}));
|
|
467
480
|
const byFile = Object.fromEntries(Object.entries(routes).map(([route, f]) => [`/${f}`, route]));
|
|
468
481
|
const rawSet = /* @__PURE__ */ new Set([`/${manifestFile.split(import_node_path.default.sep).join("/")}`, ...Object.keys(byFile)]);
|
|
@@ -487,17 +500,18 @@ function landingRoutes(app, options = {}) {
|
|
|
487
500
|
const file = routes[trimmed];
|
|
488
501
|
if (!file || read(file) == null) return next();
|
|
489
502
|
if (trimmed !== req.path && trailingSlash !== "serve") return res.redirect(301, trimmed + req.originalUrl.slice(req.path.length));
|
|
490
|
-
sendPage(req, res, file);
|
|
503
|
+
await sendPage(req, res, file, trimmed);
|
|
491
504
|
})
|
|
492
505
|
);
|
|
493
506
|
}
|
|
494
507
|
for (const [route, type] of Object.entries(CRAWLER_FILES)) {
|
|
495
508
|
app.get(route, guard(async (req, res, next) => {
|
|
496
|
-
const
|
|
497
|
-
if (
|
|
509
|
+
const raw = await isPublic(req) ? read(route.slice(1)) : null;
|
|
510
|
+
if (raw == null) {
|
|
498
511
|
if (route !== "/robots.txt") return next();
|
|
499
512
|
return res.type("text/plain").send(privateRobots);
|
|
500
513
|
}
|
|
514
|
+
const body = await bodyFor(req, raw, { route, file: route.slice(1), kind: "crawler" });
|
|
501
515
|
res.set("Cache-Control", crawlerCache);
|
|
502
516
|
res.type(type).send(fillSite(body, origin(req)));
|
|
503
517
|
}));
|
|
@@ -507,21 +521,22 @@ function landingRoutes(app, options = {}) {
|
|
|
507
521
|
function pageFile(p, { homeFile = "landing.html", pagesDir = "landing-pages" } = {}) {
|
|
508
522
|
return p === "/" ? homeFile : import_node_path.default.posix.join(pagesDir, `${p.replace(/^\/+|\/+$/g, "")}.html`);
|
|
509
523
|
}
|
|
524
|
+
function fillPage(template, p, { slot = "<!--landing-->" } = {}) {
|
|
525
|
+
if (!template || !template.includes(slot)) throw new Error(`landing-kit prerenderPages: the template has no ${slot} slot for the page body.`);
|
|
526
|
+
for (const k of ["path", "html"]) if (p?.[k] == null) throw new Error(`landing-kit prerenderPages: page "${p?.path || "?"}" has no "${k}".`);
|
|
527
|
+
const esc = escapeHtml;
|
|
528
|
+
const jsonLd = p.jsonLd == null ? "" : typeof p.jsonLd === "string" ? p.jsonLd : JSON.stringify(p.jsonLd).replace(/</g, "\\u003c");
|
|
529
|
+
const out = template.replace(/%TITLE%/g, () => esc(p.title || "")).replace(/%DESCRIPTION%/g, () => esc(p.description || "")).replace(/%PATH%/g, () => p.path).replace("%JSONLD%", () => jsonLd).replace("%HEAD%", () => p.head || "").replace(slot, () => p.html);
|
|
530
|
+
const left = /%(TITLE|DESCRIPTION|PATH|JSONLD|HEAD)%/.exec(out);
|
|
531
|
+
if (left) throw new Error(`landing-kit prerenderPages: unfilled ${left[0]} on ${p.path}`);
|
|
532
|
+
return out;
|
|
533
|
+
}
|
|
510
534
|
function prerenderPages({ dist, template = "landing.html", pages, files = {}, homeFile = "landing.html", pagesDir = "landing-pages", manifest = "landing-pages.json", slot = "<!--landing-->" }) {
|
|
511
535
|
if (!dist) throw new Error('landing-kit prerenderPages: "dist" is missing.');
|
|
512
536
|
if (!Array.isArray(pages) || !pages.length) throw new Error('landing-kit prerenderPages: "pages" is missing \u2014 pass [{ path, html, title, description }].');
|
|
513
537
|
const tpl = template.includes("<") ? template : import_node_fs.default.readFileSync(import_node_path.default.join(dist, template), "utf8");
|
|
514
538
|
if (!tpl.includes(slot)) throw new Error(`landing-kit prerenderPages: the template has no ${slot} slot for the page body.`);
|
|
515
|
-
const
|
|
516
|
-
const outputs = [];
|
|
517
|
-
for (const p of pages) {
|
|
518
|
-
for (const k of ["path", "html"]) if (p[k] == null) throw new Error(`landing-kit prerenderPages: page "${p.path || "?"}" has no "${k}".`);
|
|
519
|
-
const jsonLd = p.jsonLd == null ? "" : typeof p.jsonLd === "string" ? p.jsonLd : JSON.stringify(p.jsonLd).replace(/</g, "\\u003c");
|
|
520
|
-
const out = tpl.replace(/%TITLE%/g, () => esc(p.title || "")).replace(/%DESCRIPTION%/g, () => esc(p.description || "")).replace(/%PATH%/g, () => p.path).replace("%JSONLD%", () => jsonLd).replace("%HEAD%", () => p.head || "").replace(slot, () => p.html);
|
|
521
|
-
const left = /%(TITLE|DESCRIPTION|PATH|JSONLD|HEAD)%/.exec(out);
|
|
522
|
-
if (left) throw new Error(`landing-kit prerenderPages: unfilled ${left[0]} on ${p.path}`);
|
|
523
|
-
outputs.push([p.path, pageFile(p.path, { homeFile, pagesDir }), out]);
|
|
524
|
-
}
|
|
539
|
+
const outputs = pages.map((p) => [p.path, pageFile(p.path, { homeFile, pagesDir }), fillPage(tpl, p, { slot })]);
|
|
525
540
|
import_node_fs.default.rmSync(import_node_path.default.join(dist, pagesDir), { recursive: true, force: true });
|
|
526
541
|
const map = {};
|
|
527
542
|
let bytes = 0;
|
|
@@ -556,6 +571,7 @@ function prerenderPages({ dist, template = "landing.html", pages, files = {}, ho
|
|
|
556
571
|
buildSitemap,
|
|
557
572
|
contentFacts,
|
|
558
573
|
escapeHtml,
|
|
574
|
+
fillPage,
|
|
559
575
|
fillSite,
|
|
560
576
|
jsonLdScript,
|
|
561
577
|
landingRoutes,
|
package/dist/server.js
CHANGED
|
@@ -364,7 +364,8 @@ function landingRoutes(app, options = {}) {
|
|
|
364
364
|
crawlerCache = "public, max-age=3600",
|
|
365
365
|
privateRobots = PRIVATE_ROBOTS,
|
|
366
366
|
appUrl,
|
|
367
|
-
env = process.env
|
|
367
|
+
env = process.env,
|
|
368
|
+
transformPage
|
|
368
369
|
} = opts;
|
|
369
370
|
if (!app || typeof app.get !== "function") throw new Error("landing-kit landingRoutes: pass the Express app as the first argument.");
|
|
370
371
|
if (!dist) throw new Error('landing-kit landingRoutes: "dist" is missing \u2014 pass { dist: path.join(__dirname, "client/dist") }.');
|
|
@@ -396,9 +397,20 @@ function landingRoutes(app, options = {}) {
|
|
|
396
397
|
res.set("Cache-Control", "no-cache");
|
|
397
398
|
return res.sendFile(path.join(dist, "index.html"), (err) => err && next(err));
|
|
398
399
|
};
|
|
399
|
-
const
|
|
400
|
+
const bodyFor = async (req, body, meta) => {
|
|
401
|
+
if (!transformPage) return body;
|
|
402
|
+
try {
|
|
403
|
+
const out = await transformPage(body, { req, ...meta });
|
|
404
|
+
return typeof out === "string" ? out : body;
|
|
405
|
+
} catch (err) {
|
|
406
|
+
console.error(`landing-kit transformPage failed on ${meta.route}:`, err?.message || err);
|
|
407
|
+
return body;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const sendPage = async (req, res, file, route) => {
|
|
411
|
+
const body = await bodyFor(req, read(file), { route, file, kind: "page" });
|
|
400
412
|
res.set("Cache-Control", cacheControl);
|
|
401
|
-
res.type("html").send(fillSite(
|
|
413
|
+
res.type("html").send(fillSite(body, origin(req), { html: true, env }));
|
|
402
414
|
};
|
|
403
415
|
app.get("/", guard(async (req, res, next) => {
|
|
404
416
|
const file = routes["/"];
|
|
@@ -406,7 +418,7 @@ function landingRoutes(app, options = {}) {
|
|
|
406
418
|
if (!await isPublic(req)) return onPrivateHost === "app-shell" ? appShell(res, next) : next();
|
|
407
419
|
res.set("Vary", "Cookie");
|
|
408
420
|
if (hasSession(req)) return onSession === "app-shell" ? appShell(res, next) : next();
|
|
409
|
-
sendPage(req, res, file);
|
|
421
|
+
await sendPage(req, res, file, "/");
|
|
410
422
|
}));
|
|
411
423
|
const byFile = Object.fromEntries(Object.entries(routes).map(([route, f]) => [`/${f}`, route]));
|
|
412
424
|
const rawSet = /* @__PURE__ */ new Set([`/${manifestFile.split(path.sep).join("/")}`, ...Object.keys(byFile)]);
|
|
@@ -431,17 +443,18 @@ function landingRoutes(app, options = {}) {
|
|
|
431
443
|
const file = routes[trimmed];
|
|
432
444
|
if (!file || read(file) == null) return next();
|
|
433
445
|
if (trimmed !== req.path && trailingSlash !== "serve") return res.redirect(301, trimmed + req.originalUrl.slice(req.path.length));
|
|
434
|
-
sendPage(req, res, file);
|
|
446
|
+
await sendPage(req, res, file, trimmed);
|
|
435
447
|
})
|
|
436
448
|
);
|
|
437
449
|
}
|
|
438
450
|
for (const [route, type] of Object.entries(CRAWLER_FILES)) {
|
|
439
451
|
app.get(route, guard(async (req, res, next) => {
|
|
440
|
-
const
|
|
441
|
-
if (
|
|
452
|
+
const raw = await isPublic(req) ? read(route.slice(1)) : null;
|
|
453
|
+
if (raw == null) {
|
|
442
454
|
if (route !== "/robots.txt") return next();
|
|
443
455
|
return res.type("text/plain").send(privateRobots);
|
|
444
456
|
}
|
|
457
|
+
const body = await bodyFor(req, raw, { route, file: route.slice(1), kind: "crawler" });
|
|
445
458
|
res.set("Cache-Control", crawlerCache);
|
|
446
459
|
res.type(type).send(fillSite(body, origin(req)));
|
|
447
460
|
}));
|
|
@@ -451,21 +464,22 @@ function landingRoutes(app, options = {}) {
|
|
|
451
464
|
function pageFile(p, { homeFile = "landing.html", pagesDir = "landing-pages" } = {}) {
|
|
452
465
|
return p === "/" ? homeFile : path.posix.join(pagesDir, `${p.replace(/^\/+|\/+$/g, "")}.html`);
|
|
453
466
|
}
|
|
467
|
+
function fillPage(template, p, { slot = "<!--landing-->" } = {}) {
|
|
468
|
+
if (!template || !template.includes(slot)) throw new Error(`landing-kit prerenderPages: the template has no ${slot} slot for the page body.`);
|
|
469
|
+
for (const k of ["path", "html"]) if (p?.[k] == null) throw new Error(`landing-kit prerenderPages: page "${p?.path || "?"}" has no "${k}".`);
|
|
470
|
+
const esc = escapeHtml;
|
|
471
|
+
const jsonLd = p.jsonLd == null ? "" : typeof p.jsonLd === "string" ? p.jsonLd : JSON.stringify(p.jsonLd).replace(/</g, "\\u003c");
|
|
472
|
+
const out = template.replace(/%TITLE%/g, () => esc(p.title || "")).replace(/%DESCRIPTION%/g, () => esc(p.description || "")).replace(/%PATH%/g, () => p.path).replace("%JSONLD%", () => jsonLd).replace("%HEAD%", () => p.head || "").replace(slot, () => p.html);
|
|
473
|
+
const left = /%(TITLE|DESCRIPTION|PATH|JSONLD|HEAD)%/.exec(out);
|
|
474
|
+
if (left) throw new Error(`landing-kit prerenderPages: unfilled ${left[0]} on ${p.path}`);
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
454
477
|
function prerenderPages({ dist, template = "landing.html", pages, files = {}, homeFile = "landing.html", pagesDir = "landing-pages", manifest = "landing-pages.json", slot = "<!--landing-->" }) {
|
|
455
478
|
if (!dist) throw new Error('landing-kit prerenderPages: "dist" is missing.');
|
|
456
479
|
if (!Array.isArray(pages) || !pages.length) throw new Error('landing-kit prerenderPages: "pages" is missing \u2014 pass [{ path, html, title, description }].');
|
|
457
480
|
const tpl = template.includes("<") ? template : fs.readFileSync(path.join(dist, template), "utf8");
|
|
458
481
|
if (!tpl.includes(slot)) throw new Error(`landing-kit prerenderPages: the template has no ${slot} slot for the page body.`);
|
|
459
|
-
const
|
|
460
|
-
const outputs = [];
|
|
461
|
-
for (const p of pages) {
|
|
462
|
-
for (const k of ["path", "html"]) if (p[k] == null) throw new Error(`landing-kit prerenderPages: page "${p.path || "?"}" has no "${k}".`);
|
|
463
|
-
const jsonLd = p.jsonLd == null ? "" : typeof p.jsonLd === "string" ? p.jsonLd : JSON.stringify(p.jsonLd).replace(/</g, "\\u003c");
|
|
464
|
-
const out = tpl.replace(/%TITLE%/g, () => esc(p.title || "")).replace(/%DESCRIPTION%/g, () => esc(p.description || "")).replace(/%PATH%/g, () => p.path).replace("%JSONLD%", () => jsonLd).replace("%HEAD%", () => p.head || "").replace(slot, () => p.html);
|
|
465
|
-
const left = /%(TITLE|DESCRIPTION|PATH|JSONLD|HEAD)%/.exec(out);
|
|
466
|
-
if (left) throw new Error(`landing-kit prerenderPages: unfilled ${left[0]} on ${p.path}`);
|
|
467
|
-
outputs.push([p.path, pageFile(p.path, { homeFile, pagesDir }), out]);
|
|
468
|
-
}
|
|
482
|
+
const outputs = pages.map((p) => [p.path, pageFile(p.path, { homeFile, pagesDir }), fillPage(tpl, p, { slot })]);
|
|
469
483
|
fs.rmSync(path.join(dist, pagesDir), { recursive: true, force: true });
|
|
470
484
|
const map = {};
|
|
471
485
|
let bytes = 0;
|
|
@@ -499,6 +513,7 @@ export {
|
|
|
499
513
|
buildSitemap,
|
|
500
514
|
contentFacts,
|
|
501
515
|
escapeHtml,
|
|
516
|
+
fillPage,
|
|
502
517
|
fillSite,
|
|
503
518
|
jsonLdScript,
|
|
504
519
|
landingRoutes,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreamtree-org/landing-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The shared marketing site and auth frame (AuthShell + brands) for every Dreamtree product: content-driven landing pages, login/signup shell, Express routes, prerender and SEO files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/server.d.ts
CHANGED
|
@@ -31,13 +31,21 @@ export interface LandingRoutesOptions {
|
|
|
31
31
|
privateRobots?: string;
|
|
32
32
|
appUrl?: string;
|
|
33
33
|
env?: Record<string, string | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Runs on every page ('page') and crawler file ('crawler') before the origin is filled in,
|
|
36
|
+
* e.g. to re-render live prices. null/undefined or a throw serves the prerendered body.
|
|
37
|
+
*/
|
|
38
|
+
transformPage?: (body: string, meta: { req: any; route: string; file: string; kind: 'page' | 'crawler' }) => string | null | undefined | Promise<string | null | undefined>;
|
|
34
39
|
}
|
|
40
|
+
export interface PrerenderPage { path: string; html: string; title?: string; description?: string; jsonLd?: unknown; head?: string }
|
|
41
|
+
/** One page poured into the template: what prerenderPages writes, as a string. */
|
|
42
|
+
export declare function fillPage(template: string, page: PrerenderPage, opts?: { slot?: string }): string;
|
|
35
43
|
export declare function landingRoutes(app: any, options: LandingRoutesOptions | string): string[];
|
|
36
44
|
export declare function pageFile(path: string, opts?: { homeFile?: string; pagesDir?: string }): string;
|
|
37
45
|
export declare function prerenderPages(args: {
|
|
38
46
|
dist: string;
|
|
39
47
|
template?: string;
|
|
40
|
-
pages:
|
|
48
|
+
pages: PrerenderPage[];
|
|
41
49
|
files?: Record<string, string | null | undefined>;
|
|
42
50
|
homeFile?: string; pagesDir?: string; manifest?: string; slot?: string;
|
|
43
51
|
}): { manifest: Record<string, string>; bytes: number };
|