@rimelight/seo 0.0.7 → 0.0.9
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/dist/index.d.mts +2 -2
- package/dist/integration.mjs +58 -29
- package/dist/routes/open-graph._...route_.png.d.mts +5 -0
- package/dist/routes/open-graph._...route_.png.mjs +57 -0
- package/dist/routes/sitemap-index.xml.d.mts +4 -0
- package/dist/routes/sitemap-index.xml.mjs +16 -0
- package/dist/types.d.mts +75 -0
- package/package.json +4 -4
- package/src/env.d.ts +66 -56
- package/src/index.ts +1 -0
- package/src/integration.ts +84 -25
- package/src/routes/open-graph.[...route].png.ts +81 -0
- package/src/routes/sitemap-index.xml.ts +20 -0
- package/src/types.ts +76 -0
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { generateIco, generateMaskablePng, generateOgCard, generatePng } from "./assets.mjs";
|
|
2
2
|
import { i as buildBreadcrumbSchema, n as BreadcrumbItem, r as buildArticleSchema, t as ArticleSchemaOptions } from "./schema-C8v69blQ.mjs";
|
|
3
3
|
import { a as buildLlmsTxt, i as buildLlmsFullTxt, n as LlmsPage, r as LlmsTxtOptions, t as LlmsFullTxtOptions } from "./llms-BVLxzjr-.mjs";
|
|
4
|
-
import { ApiCatalog, ApiCatalogItem, ApiCatalogOptions, ApiCatalogRouteOptions, AssetRouteOptions, ChangeFreq, HeadProps, LlmsFullRouteOptions, LlmsRouteOptions, LocaleConfig, ManifestRouteOptions, OGImage, OpenGraphMeta, RSSFeed, RimelightSeoOptions, RobotsOptions, RobotsRouteOptions, RssCollectionConfig, RssFeedOptions, RssItem, RssOptions, SecurityRouteOptions, SecurityTxtOptions, SeoEntry, SiteConfig, SitemapAlternate, SitemapOptions, SitemapUrl, TwitterMeta, UserAgentRule, WebManifest, WebManifestIcon, WebManifestOptions, WebManifestShortcut } from "./types.mjs";
|
|
4
|
+
import { ApiCatalog, ApiCatalogItem, ApiCatalogOptions, ApiCatalogRouteOptions, AssetRouteOptions, ChangeFreq, HeadProps, LlmsFullRouteOptions, LlmsRouteOptions, LocaleConfig, ManifestRouteOptions, OGImage, OgRouteOptions, OpenGraphMeta, RSSFeed, RimelightSeoOptions, RobotsOptions, RobotsRouteOptions, RssCollectionConfig, RssFeedOptions, RssItem, RssOptions, SecurityRouteOptions, SecurityTxtOptions, SeoEntry, SiteConfig, SitemapAlternate, SitemapOptions, SitemapUrl, TwitterMeta, UserAgentRule, WebManifest, WebManifestIcon, WebManifestOptions, WebManifestShortcut } from "./types.mjs";
|
|
5
5
|
import { buildApiCatalog } from "./catalog.mjs";
|
|
6
6
|
import { buildSitemapIndexXml, buildSitemapUrls, buildSitemapXml, chunkSitemapUrls } from "./sitemap.mjs";
|
|
7
7
|
import { buildRobotsTxt } from "./robots.mjs";
|
|
@@ -11,4 +11,4 @@ import { buildSecurityTxt } from "./security.mjs";
|
|
|
11
11
|
import { buildWebManifest } from "./manifest.mjs";
|
|
12
12
|
import { OgFontConfig, OgLayoutOptions, OgRenderConfig } from "./og.mjs";
|
|
13
13
|
import rimelightSeo from "./integration.mjs";
|
|
14
|
-
export { type ApiCatalog, type ApiCatalogItem, type ApiCatalogOptions, type ApiCatalogRouteOptions, type ArticleSchemaOptions, type AssetRouteOptions, type BreadcrumbItem, type ChangeFreq, type HeadProps, type LlmsFullRouteOptions, type LlmsFullTxtOptions, type LlmsPage, type LlmsRouteOptions, type LlmsTxtOptions, type LocaleConfig, type ManifestRouteOptions, type OGImage, type OgFontConfig, type OgLayoutOptions, type OgRenderConfig, type OpenGraphMeta, type RSSFeed, type RimelightSeoOptions, type RobotsOptions, type RobotsRouteOptions, type RssCollectionConfig, type RssFeedOptions, type RssItem, type RssOptions, type SecurityRouteOptions, type SecurityTxtOptions, type SeoEntry, type SiteConfig, type SitemapAlternate, type SitemapOptions, type SitemapUrl, type TwitterMeta, type UserAgentRule, type WebManifest, type WebManifestIcon, type WebManifestOptions, type WebManifestShortcut, buildApiCatalog, buildArticleSchema, buildBreadcrumbSchema, buildCanonicalUrl, buildLlmsFullTxt, buildLlmsTxt, buildPageTitle, buildRobotsMetaContent, buildRobotsTxt, buildRssXml, buildSafeDescription, buildSecurityTxt, buildSitemapIndexXml, buildSitemapUrls, buildSitemapXml, buildWebManifest, buildWebSiteSchema, chunkSitemapUrls, deepMerge, rimelightSeo as default, rimelightSeo, escapeXml, generateIco, generateMaskablePng, generateOgCard, generatePng };
|
|
14
|
+
export { type ApiCatalog, type ApiCatalogItem, type ApiCatalogOptions, type ApiCatalogRouteOptions, type ArticleSchemaOptions, type AssetRouteOptions, type BreadcrumbItem, type ChangeFreq, type HeadProps, type LlmsFullRouteOptions, type LlmsFullTxtOptions, type LlmsPage, type LlmsRouteOptions, type LlmsTxtOptions, type LocaleConfig, type ManifestRouteOptions, type OGImage, type OgFontConfig, type OgLayoutOptions, type OgRenderConfig, type OgRouteOptions, type OpenGraphMeta, type RSSFeed, type RimelightSeoOptions, type RobotsOptions, type RobotsRouteOptions, type RssCollectionConfig, type RssFeedOptions, type RssItem, type RssOptions, type SecurityRouteOptions, type SecurityTxtOptions, type SeoEntry, type SiteConfig, type SitemapAlternate, type SitemapOptions, type SitemapUrl, type TwitterMeta, type UserAgentRule, type WebManifest, type WebManifestIcon, type WebManifestOptions, type WebManifestShortcut, buildApiCatalog, buildArticleSchema, buildBreadcrumbSchema, buildCanonicalUrl, buildLlmsFullTxt, buildLlmsTxt, buildPageTitle, buildRobotsMetaContent, buildRobotsTxt, buildRssXml, buildSafeDescription, buildSecurityTxt, buildSitemapIndexXml, buildSitemapUrls, buildSitemapXml, buildWebManifest, buildWebSiteSchema, chunkSitemapUrls, deepMerge, rimelightSeo as default, rimelightSeo, escapeXml, generateIco, generateMaskablePng, generateOgCard, generatePng };
|
package/dist/integration.mjs
CHANGED
|
@@ -15,16 +15,35 @@ function rimelightSeo(options = {}) {
|
|
|
15
15
|
}
|
|
16
16
|
return null;
|
|
17
17
|
};
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
const siteOpt = options.site || {};
|
|
19
|
+
const normalizedSiteConfig = {
|
|
20
|
+
id: siteOpt.id || options.id || "",
|
|
21
|
+
name: siteOpt.name || options.name || "",
|
|
22
|
+
description: siteOpt.description || options.description || "",
|
|
23
|
+
url: siteOpt.url || options.url || "",
|
|
24
|
+
ogImage: siteOpt.ogImage || options.ogImage || void 0,
|
|
25
|
+
author: siteOpt.author || options.author || siteOpt.name || options.name || "",
|
|
26
|
+
email: siteOpt.email || options.email || "",
|
|
27
|
+
branding: {
|
|
28
|
+
logo: { alt: siteOpt.branding?.logo?.alt || options.branding?.logo?.alt || siteOpt.name || options.name || "" },
|
|
29
|
+
colors: {
|
|
30
|
+
themeColor: siteOpt.branding?.colors?.themeColor || options.branding?.colors?.themeColor || "#ffffff",
|
|
31
|
+
backgroundColor: siteOpt.branding?.colors?.backgroundColor || options.branding?.colors?.backgroundColor || "#ffffff"
|
|
32
|
+
},
|
|
33
|
+
...siteOpt.branding,
|
|
34
|
+
...options.branding
|
|
35
|
+
},
|
|
36
|
+
seo: {
|
|
37
|
+
titleTemplate: siteOpt.seo?.titleTemplate || options.titleTemplate || (options.name ? `%s | ${options.name}` : "%s"),
|
|
38
|
+
maxDescriptionLength: siteOpt.seo?.maxDescriptionLength || options.maxDescriptionLength || 160,
|
|
39
|
+
authorHandle: siteOpt.seo?.authorHandle || options.authorHandle || void 0,
|
|
40
|
+
...siteOpt.seo
|
|
41
|
+
},
|
|
42
|
+
...siteOpt.openGraph || options.openGraph ? { openGraph: siteOpt.openGraph || options.openGraph } : {},
|
|
43
|
+
...siteOpt.twitter || options.twitter ? { twitter: siteOpt.twitter || options.twitter } : {}
|
|
44
|
+
};
|
|
45
|
+
const seoLocales = options.locales;
|
|
46
|
+
const privatePathPrefixes = options.privatePathPrefixes || [];
|
|
28
47
|
const dbPath = findFile([
|
|
29
48
|
"src/db/index.ts",
|
|
30
49
|
"src/db/index.js",
|
|
@@ -58,10 +77,16 @@ function rimelightSeo(options = {}) {
|
|
|
58
77
|
pattern: typeof options.robots === "object" && options.robots.path ? options.robots.path : "/robots.txt",
|
|
59
78
|
entrypoint: "@rimelight/seo/routes/robots.txt.ts"
|
|
60
79
|
});
|
|
61
|
-
if (options.sitemap !== false)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
if (options.sitemap !== false) {
|
|
81
|
+
injectRoute({
|
|
82
|
+
pattern: typeof options.sitemap === "object" && options.sitemap.path ? options.sitemap.path : "/sitemap.xml",
|
|
83
|
+
entrypoint: "@rimelight/seo/routes/sitemap.xml.ts"
|
|
84
|
+
});
|
|
85
|
+
injectRoute({
|
|
86
|
+
pattern: "/sitemap-index.xml",
|
|
87
|
+
entrypoint: "@rimelight/seo/routes/sitemap-index.xml.ts"
|
|
88
|
+
});
|
|
89
|
+
}
|
|
65
90
|
if (options.rss !== false) injectRoute({
|
|
66
91
|
pattern: typeof options.rss === "object" && options.rss.path ? options.rss.path : "/rss.xml",
|
|
67
92
|
entrypoint: "@rimelight/seo/routes/rss.xml.ts"
|
|
@@ -93,6 +118,10 @@ function rimelightSeo(options = {}) {
|
|
|
93
118
|
pattern: typeof options.manifest === "object" && options.manifest.path ? options.manifest.path : "/site.webmanifest",
|
|
94
119
|
entrypoint: "@rimelight/seo/routes/site.webmanifest.ts"
|
|
95
120
|
});
|
|
121
|
+
if (options.og !== false) injectRoute({
|
|
122
|
+
pattern: typeof options.og === "object" && options.og.path ? options.og.path : "/open-graph/[...route].png",
|
|
123
|
+
entrypoint: "@rimelight/seo/routes/open-graph.[...route].png.ts"
|
|
124
|
+
});
|
|
96
125
|
updateConfig({ vite: { plugins: [{
|
|
97
126
|
name: "vite-plugin-rimelight-seo-assets",
|
|
98
127
|
configureServer(server) {
|
|
@@ -240,6 +269,7 @@ function rimelightSeo(options = {}) {
|
|
|
240
269
|
name: "vite-plugin-rimelight-seo",
|
|
241
270
|
resolveId(id) {
|
|
242
271
|
if (id === "virtual:rimelight-seo-config" || id === "virtual:rimelight-seo/options") return "\0virtual:rimelight-seo/options";
|
|
272
|
+
if (id === "virtual:rimelight/seo") return "\0virtual:rimelight/seo";
|
|
243
273
|
if (id === "virtual:rimelight-seo/config") return "\0virtual:rimelight-seo/config";
|
|
244
274
|
if (id === "virtual:rimelight-seo/site-config") return "\0virtual:rimelight-seo/site-config";
|
|
245
275
|
if (id === "virtual:rimelight-seo/db") return "\0virtual:rimelight-seo/db";
|
|
@@ -248,22 +278,21 @@ function rimelightSeo(options = {}) {
|
|
|
248
278
|
},
|
|
249
279
|
load(id) {
|
|
250
280
|
if (id === "\0virtual:rimelight-seo/options") return `export default ${JSON.stringify(options)};`;
|
|
251
|
-
if (id === "\0virtual:rimelight
|
|
252
|
-
|
|
253
|
-
export const
|
|
254
|
-
export const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
export const
|
|
281
|
+
if (id === "\0virtual:rimelight/seo") return `export const siteConfig = ${JSON.stringify(normalizedSiteConfig)};
|
|
282
|
+
export const SEO_LOCALES = ${JSON.stringify(seoLocales)};
|
|
283
|
+
export const PRIVATE_PATH_PREFIXES = ${JSON.stringify(privatePathPrefixes)};
|
|
284
|
+
export const seoConfig = {
|
|
285
|
+
SEO_LOCALES: ${JSON.stringify(seoLocales)},
|
|
286
|
+
PRIVATE_PATH_PREFIXES: ${JSON.stringify(privatePathPrefixes)}
|
|
287
|
+
};
|
|
288
|
+
export default siteConfig;`;
|
|
289
|
+
if (id === "\0virtual:rimelight-seo/config") return `export const SEO_LOCALES = ${JSON.stringify(seoLocales)};
|
|
290
|
+
export const PRIVATE_PATH_PREFIXES = ${JSON.stringify(privatePathPrefixes)};
|
|
260
291
|
export const seoEntries = async () => [];
|
|
261
|
-
export const rssEntries = undefined
|
|
262
|
-
|
|
263
|
-
if (id === "\0virtual:rimelight-seo/site-config") {
|
|
264
|
-
|
|
265
|
-
return `export const siteConfig = { name: "Site", description: "", url: "" };`;
|
|
266
|
-
}
|
|
292
|
+
export const rssEntries = undefined;
|
|
293
|
+
export default { SEO_LOCALES: ${JSON.stringify(seoLocales)}, PRIVATE_PATH_PREFIXES: ${JSON.stringify(privatePathPrefixes)} };`;
|
|
294
|
+
if (id === "\0virtual:rimelight-seo/site-config") return `export const siteConfig = ${JSON.stringify(normalizedSiteConfig)};
|
|
295
|
+
export default siteConfig;`;
|
|
267
296
|
if (id === "\0virtual:rimelight-seo/db") {
|
|
268
297
|
if (dbPath) return `export * from ${JSON.stringify(dbPath)};`;
|
|
269
298
|
return `export const db = null;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { renderDefaultOg } from "../og.mjs";
|
|
2
|
+
import seoOptions from "virtual:rimelight-seo/options";
|
|
3
|
+
import * as siteConfigMod from "virtual:rimelight-seo/site-config";
|
|
4
|
+
import * as dbMod from "virtual:rimelight-seo/db";
|
|
5
|
+
//#region src/routes/open-graph.[...route].png.ts
|
|
6
|
+
const prerender = false;
|
|
7
|
+
function getLocalizedText(val, locale = "en") {
|
|
8
|
+
if (typeof val === "object" && val !== null) {
|
|
9
|
+
const record = val;
|
|
10
|
+
return record[locale] || Object.values(record)[0] || "";
|
|
11
|
+
}
|
|
12
|
+
if (typeof val === "string") return val;
|
|
13
|
+
if (typeof val === "number" || typeof val === "boolean") return String(val);
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
const GET = async ({ request }) => {
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const routeParam = url.pathname.replace(/^\/open-graph\//, "").replace(/\.png$/, "");
|
|
19
|
+
const siteConfig = siteConfigMod.siteConfig || {};
|
|
20
|
+
const ogOpts = typeof seoOptions?.og === "object" ? seoOptions.og : {};
|
|
21
|
+
const brand = ogOpts.brand || seoOptions?.name || siteConfig.name || url.hostname;
|
|
22
|
+
const defaultBg = ogOpts.defaultBackground ?? "dark";
|
|
23
|
+
const docsBg = ogOpts.docsBackground ?? "gradient";
|
|
24
|
+
let title = url.searchParams.get("title") || "";
|
|
25
|
+
let description = url.searchParams.get("description") || "";
|
|
26
|
+
let date = url.searchParams.get("pubDate") || "";
|
|
27
|
+
const badge = url.searchParams.get("type") || (routeParam.includes("blog") ? "Blog Post" : routeParam.includes("legal") ? "Legal" : "");
|
|
28
|
+
if (!title && routeParam && routeParam !== "page" && routeParam !== "default" && dbMod.db) try {
|
|
29
|
+
const { pages: pagesTable } = await import("#db/schema").catch(() => ({ pages: null }));
|
|
30
|
+
const { eq, and, isNull } = await import("drizzle-orm");
|
|
31
|
+
if (pagesTable) {
|
|
32
|
+
const slug = routeParam.split("/").pop() ?? "";
|
|
33
|
+
const page = await dbMod.db.select({
|
|
34
|
+
title: pagesTable.title,
|
|
35
|
+
description: pagesTable.description,
|
|
36
|
+
postedAt: pagesTable.postedAt
|
|
37
|
+
}).from(pagesTable).where(and(eq(pagesTable.slug, slug), isNull(pagesTable.deletedAt))).limit(1).then((r) => r[0]).catch(() => void 0);
|
|
38
|
+
if (page) {
|
|
39
|
+
title = getLocalizedText(page.title);
|
|
40
|
+
description = description || getLocalizedText(page.description);
|
|
41
|
+
if (page.postedAt && !date) date = new Date(page.postedAt).toLocaleDateString();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch {}
|
|
45
|
+
const isDocs = url.searchParams.get("isDocs") === "true" || routeParam.includes("docs");
|
|
46
|
+
return renderDefaultOg({
|
|
47
|
+
title: title || brand,
|
|
48
|
+
description,
|
|
49
|
+
brand,
|
|
50
|
+
badge,
|
|
51
|
+
date,
|
|
52
|
+
background: isDocs ? docsBg : defaultBg,
|
|
53
|
+
preview: url.searchParams.get("preview") === "1"
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
//#endregion
|
|
57
|
+
export { GET, prerender };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { buildSitemapIndexXml } from "../sitemap.mjs";
|
|
2
|
+
import seoOptions from "virtual:rimelight-seo/options";
|
|
3
|
+
import * as siteConfigMod from "virtual:rimelight-seo/site-config";
|
|
4
|
+
//#region src/routes/sitemap-index.xml.ts
|
|
5
|
+
const GET = ({ site, url }) => {
|
|
6
|
+
const siteUrl = site ? site.toString() : siteConfigMod.siteConfig?.url || url.origin;
|
|
7
|
+
const sitemapOpts = typeof seoOptions?.sitemap === "object" ? seoOptions.sitemap : {};
|
|
8
|
+
const sitemapPattern = typeof sitemapOpts.path === "string" ? sitemapOpts.path : "/sitemap.xml";
|
|
9
|
+
const xml = buildSitemapIndexXml([new URL(sitemapPattern, siteUrl).toString()]);
|
|
10
|
+
return new Response(xml, { headers: {
|
|
11
|
+
"Content-Type": "application/xml; charset=utf-8",
|
|
12
|
+
"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400"
|
|
13
|
+
} });
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { GET };
|
package/dist/types.d.mts
CHANGED
|
@@ -493,6 +493,47 @@ export interface AssetRouteOptions {
|
|
|
493
493
|
ogImage?: boolean;
|
|
494
494
|
}
|
|
495
495
|
export interface RimelightSeoOptions {
|
|
496
|
+
/**
|
|
497
|
+
* Site identifier / domain (e.g. "rimelight.com")
|
|
498
|
+
*/
|
|
499
|
+
id?: string;
|
|
500
|
+
/**
|
|
501
|
+
* Primary site name (e.g. "Rimelight")
|
|
502
|
+
*/
|
|
503
|
+
name?: string;
|
|
504
|
+
/**
|
|
505
|
+
* Default site description
|
|
506
|
+
*/
|
|
507
|
+
description?: string;
|
|
508
|
+
/**
|
|
509
|
+
* Canonical origin URL (e.g. "https://rimelight.com")
|
|
510
|
+
*/
|
|
511
|
+
url?: string;
|
|
512
|
+
ogImage?: string;
|
|
513
|
+
author?: string;
|
|
514
|
+
email?: string;
|
|
515
|
+
branding?: Partial<SiteConfig["branding"]>;
|
|
516
|
+
titleTemplate?: string;
|
|
517
|
+
maxDescriptionLength?: number;
|
|
518
|
+
authorHandle?: string;
|
|
519
|
+
openGraph?: OpenGraphMeta;
|
|
520
|
+
twitter?: TwitterMeta;
|
|
521
|
+
/**
|
|
522
|
+
* Full or partial SiteConfig object
|
|
523
|
+
*/
|
|
524
|
+
site?: Partial<SiteConfig>;
|
|
525
|
+
/**
|
|
526
|
+
* Locale mappings for hreflang / sitemaps
|
|
527
|
+
*/
|
|
528
|
+
locales?: LocaleConfig | Record<string, string>;
|
|
529
|
+
/**
|
|
530
|
+
* Path prefixes to exclude from sitemap and indexing
|
|
531
|
+
*/
|
|
532
|
+
privatePathPrefixes?: string[];
|
|
533
|
+
/**
|
|
534
|
+
* Custom SEO entry generator function
|
|
535
|
+
*/
|
|
536
|
+
entries?: () => Promise<SeoEntry[]> | SeoEntry[];
|
|
496
537
|
/**
|
|
497
538
|
* Sitemap configuration or false to disable automatic /sitemap.xml (default: true)
|
|
498
539
|
*/
|
|
@@ -532,6 +573,40 @@ export interface RimelightSeoOptions {
|
|
|
532
573
|
* icons) from a single master icon (default: true)
|
|
533
574
|
*/
|
|
534
575
|
assets?: boolean | AssetRouteOptions;
|
|
576
|
+
/**
|
|
577
|
+
* Open Graph image route configuration, or false to disable automatic /open-graph/[...route].png
|
|
578
|
+
* (default: true). When enabled, the route reads brand/background from seo.name and injects a
|
|
579
|
+
* handler that supports query-param overrides and optional DB slug lookup.
|
|
580
|
+
*/
|
|
581
|
+
og?: boolean | OgRouteOptions;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Configuration for the auto-injected /open-graph/[...route].png route.
|
|
585
|
+
*/
|
|
586
|
+
export interface OgRouteOptions {
|
|
587
|
+
/**
|
|
588
|
+
* Route pattern for the OG image handler.
|
|
589
|
+
*
|
|
590
|
+
* @default "/open-graph/[...route].png"
|
|
591
|
+
*/
|
|
592
|
+
path?: string;
|
|
593
|
+
/**
|
|
594
|
+
* Brand name shown top-left of every OG image. Defaults to seo.name / site.name from integration
|
|
595
|
+
* config.
|
|
596
|
+
*/
|
|
597
|
+
brand?: string;
|
|
598
|
+
/**
|
|
599
|
+
* Background style used for standard pages.
|
|
600
|
+
*
|
|
601
|
+
* @default "dark"
|
|
602
|
+
*/
|
|
603
|
+
defaultBackground?: "dark" | "gradient" | (string & {});
|
|
604
|
+
/**
|
|
605
|
+
* Background style used when ?isDocs=true or the route segment contains "docs".
|
|
606
|
+
*
|
|
607
|
+
* @default "gradient"
|
|
608
|
+
*/
|
|
609
|
+
docsBackground?: "dark" | "gradient" | (string & {});
|
|
535
610
|
}
|
|
536
611
|
//#endregion
|
|
537
612
|
export type { ArticleSchemaOptions, BreadcrumbItem, LlmsFullTxtOptions, LlmsPage, LlmsTxtOptions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/seo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment's SEO Package",
|
|
6
6
|
"homepage": "https://rimelight.com/docs",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"access": "public"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@rimelight/config": "0.0.
|
|
77
|
-
"astro": "7.3.
|
|
76
|
+
"@rimelight/config": "0.0.12",
|
|
77
|
+
"astro": "7.3.3",
|
|
78
78
|
"sharp": "^0.35.4",
|
|
79
79
|
"typescript": "6.0.3"
|
|
80
80
|
},
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"engines": {
|
|
95
|
-
"node": ">=26.
|
|
95
|
+
"node": ">=26.9.0"
|
|
96
96
|
},
|
|
97
97
|
"scripts": {
|
|
98
98
|
"build": "vp pack",
|
package/src/env.d.ts
CHANGED
|
@@ -1,56 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export default
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
declare module "virtual:rimelight-seo
|
|
13
|
-
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export const
|
|
25
|
-
export const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
export const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
export const
|
|
56
|
-
}
|
|
1
|
+
declare module "virtual:rimelight/seo" {
|
|
2
|
+
export const siteConfig: import("./types.js").SiteConfig
|
|
3
|
+
export const SEO_LOCALES: import("./types.js").LocaleConfig | Record<string, string> | undefined
|
|
4
|
+
export const PRIVATE_PATH_PREFIXES: string[]
|
|
5
|
+
export const seoConfig: {
|
|
6
|
+
SEO_LOCALES?: import("./types.js").LocaleConfig | Record<string, string>
|
|
7
|
+
PRIVATE_PATH_PREFIXES: string[]
|
|
8
|
+
}
|
|
9
|
+
export default siteConfig
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module "virtual:rimelight-seo-config" {
|
|
13
|
+
const config: import("./integration.js").RimelightSeoOptions
|
|
14
|
+
export default config
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare module "virtual:rimelight-seo/options" {
|
|
18
|
+
const config: import("./integration.js").RimelightSeoOptions
|
|
19
|
+
export default config
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module "virtual:rimelight-seo/config" {
|
|
23
|
+
export const SEO_LOCALES: import("./types.js").LocaleConfig | undefined
|
|
24
|
+
export const PRIVATE_PATH_PREFIXES: string[] | undefined
|
|
25
|
+
export const seoEntries: (() => Promise<import("./types.js").SeoEntry[]>) | undefined
|
|
26
|
+
export const rssEntries: (() => Promise<import("./types.js").RssItem[]>) | undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare module "virtual:rimelight-seo/site-config" {
|
|
30
|
+
export const siteConfig: import("./types.js").SiteConfig | Record<string, any>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare module "virtual:rimelight-seo/db" {
|
|
34
|
+
export const db: any
|
|
35
|
+
export const pages: any
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare module "virtual:rimelight-seo/corpus" {
|
|
39
|
+
export const corpusPages: any[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare module "#db/schema" {
|
|
43
|
+
export const pages: any
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare module "drizzle-orm" {
|
|
47
|
+
export const and: (...args: any[]) => any
|
|
48
|
+
export const isNull: (column: any) => any
|
|
49
|
+
export const isNotNull: (column: any) => any
|
|
50
|
+
export const sql: any
|
|
51
|
+
export const eq: (column: any, value: any) => any
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare module "@rimelight/cms" {
|
|
55
|
+
export const renderCorpusMarkdown: (db: any, options: any) => Promise<string>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
declare module "astro:content" {
|
|
59
|
+
export const getCollection: (collection: string) => Promise<any[]>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
declare module "virtual:rimelight-seo/assets" {
|
|
63
|
+
export const masterIconBuffer: Buffer | null
|
|
64
|
+
export const isSvg: boolean
|
|
65
|
+
export const backgroundColor: string
|
|
66
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export { buildApiCatalog } from "./catalog.js"
|
|
|
66
66
|
export { buildArticleSchema, buildBreadcrumbSchema } from "./schema.js"
|
|
67
67
|
export { generatePng, generateMaskablePng, generateIco, generateOgCard } from "./assets.js"
|
|
68
68
|
export type { OgFontConfig, OgLayoutOptions, OgRenderConfig } from "./og.js"
|
|
69
|
+
export type { OgRouteOptions } from "./types.js"
|
|
69
70
|
|
|
70
71
|
import { rimelightSeo } from "./integration.js"
|
|
71
72
|
export type { RimelightSeoOptions } from "./integration.js"
|
package/src/integration.ts
CHANGED
|
@@ -23,17 +23,57 @@ export function rimelightSeo(options: RimelightSeoOptions = {}): AstroIntegratio
|
|
|
23
23
|
return null
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
const siteOpt = options.site || {}
|
|
27
|
+
const normalizedSiteConfig = {
|
|
28
|
+
id: siteOpt.id || options.id || "",
|
|
29
|
+
name: siteOpt.name || options.name || "",
|
|
30
|
+
description: siteOpt.description || options.description || "",
|
|
31
|
+
url: siteOpt.url || options.url || "",
|
|
32
|
+
ogImage: siteOpt.ogImage || options.ogImage || undefined,
|
|
33
|
+
author: siteOpt.author || options.author || siteOpt.name || options.name || "",
|
|
34
|
+
email: siteOpt.email || options.email || "",
|
|
35
|
+
branding: {
|
|
36
|
+
logo: {
|
|
37
|
+
alt:
|
|
38
|
+
siteOpt.branding?.logo?.alt ||
|
|
39
|
+
options.branding?.logo?.alt ||
|
|
40
|
+
siteOpt.name ||
|
|
41
|
+
options.name ||
|
|
42
|
+
""
|
|
43
|
+
},
|
|
44
|
+
colors: {
|
|
45
|
+
themeColor:
|
|
46
|
+
siteOpt.branding?.colors?.themeColor ||
|
|
47
|
+
options.branding?.colors?.themeColor ||
|
|
48
|
+
"#ffffff",
|
|
49
|
+
backgroundColor:
|
|
50
|
+
siteOpt.branding?.colors?.backgroundColor ||
|
|
51
|
+
options.branding?.colors?.backgroundColor ||
|
|
52
|
+
"#ffffff"
|
|
53
|
+
},
|
|
54
|
+
...siteOpt.branding,
|
|
55
|
+
...options.branding
|
|
56
|
+
},
|
|
57
|
+
seo: {
|
|
58
|
+
titleTemplate:
|
|
59
|
+
siteOpt.seo?.titleTemplate ||
|
|
60
|
+
options.titleTemplate ||
|
|
61
|
+
(options.name ? `%s | ${options.name}` : "%s"),
|
|
62
|
+
maxDescriptionLength:
|
|
63
|
+
siteOpt.seo?.maxDescriptionLength || options.maxDescriptionLength || 160,
|
|
64
|
+
authorHandle: siteOpt.seo?.authorHandle || options.authorHandle || undefined,
|
|
65
|
+
...siteOpt.seo
|
|
66
|
+
},
|
|
67
|
+
...(siteOpt.openGraph || options.openGraph
|
|
68
|
+
? { openGraph: siteOpt.openGraph || options.openGraph }
|
|
69
|
+
: {}),
|
|
70
|
+
...(siteOpt.twitter || options.twitter
|
|
71
|
+
? { twitter: siteOpt.twitter || options.twitter }
|
|
72
|
+
: {})
|
|
73
|
+
}
|
|
31
74
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
"src/config/site.config.js",
|
|
35
|
-
"src/config/site.config.mjs"
|
|
36
|
-
])
|
|
75
|
+
const seoLocales = options.locales
|
|
76
|
+
const privatePathPrefixes = options.privatePathPrefixes || []
|
|
37
77
|
|
|
38
78
|
const dbPath = findFile(["src/db/index.ts", "src/db/index.js", "src/db/index.mjs"])
|
|
39
79
|
|
|
@@ -90,6 +130,10 @@ export function rimelightSeo(options: RimelightSeoOptions = {}): AstroIntegratio
|
|
|
90
130
|
pattern,
|
|
91
131
|
entrypoint: "@rimelight/seo/routes/sitemap.xml.ts"
|
|
92
132
|
})
|
|
133
|
+
injectRoute({
|
|
134
|
+
pattern: "/sitemap-index.xml",
|
|
135
|
+
entrypoint: "@rimelight/seo/routes/sitemap-index.xml.ts"
|
|
136
|
+
})
|
|
93
137
|
}
|
|
94
138
|
|
|
95
139
|
if (options.rss !== false) {
|
|
@@ -163,6 +207,17 @@ export function rimelightSeo(options: RimelightSeoOptions = {}): AstroIntegratio
|
|
|
163
207
|
})
|
|
164
208
|
}
|
|
165
209
|
|
|
210
|
+
if (options.og !== false) {
|
|
211
|
+
const pattern =
|
|
212
|
+
typeof options.og === "object" && options.og.path
|
|
213
|
+
? options.og.path
|
|
214
|
+
: "/open-graph/[...route].png"
|
|
215
|
+
injectRoute({
|
|
216
|
+
pattern,
|
|
217
|
+
entrypoint: "@rimelight/seo/routes/open-graph.[...route].png.ts"
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
166
221
|
updateConfig({
|
|
167
222
|
vite: {
|
|
168
223
|
plugins: [
|
|
@@ -288,6 +343,9 @@ export function rimelightSeo(options: RimelightSeoOptions = {}): AstroIntegratio
|
|
|
288
343
|
) {
|
|
289
344
|
return "\0virtual:rimelight-seo/options"
|
|
290
345
|
}
|
|
346
|
+
if (id === "virtual:rimelight/seo") {
|
|
347
|
+
return "\0virtual:rimelight/seo"
|
|
348
|
+
}
|
|
291
349
|
if (id === "virtual:rimelight-seo/config") {
|
|
292
350
|
return "\0virtual:rimelight-seo/config"
|
|
293
351
|
}
|
|
@@ -306,25 +364,26 @@ export function rimelightSeo(options: RimelightSeoOptions = {}): AstroIntegratio
|
|
|
306
364
|
if (id === "\0virtual:rimelight-seo/options") {
|
|
307
365
|
return `export default ${JSON.stringify(options)};`
|
|
308
366
|
}
|
|
367
|
+
if (id === "\0virtual:rimelight/seo") {
|
|
368
|
+
return `export const siteConfig = ${JSON.stringify(normalizedSiteConfig)};
|
|
369
|
+
export const SEO_LOCALES = ${JSON.stringify(seoLocales)};
|
|
370
|
+
export const PRIVATE_PATH_PREFIXES = ${JSON.stringify(privatePathPrefixes)};
|
|
371
|
+
export const seoConfig = {
|
|
372
|
+
SEO_LOCALES: ${JSON.stringify(seoLocales)},
|
|
373
|
+
PRIVATE_PATH_PREFIXES: ${JSON.stringify(privatePathPrefixes)}
|
|
374
|
+
};
|
|
375
|
+
export default siteConfig;`
|
|
376
|
+
}
|
|
309
377
|
if (id === "\0virtual:rimelight-seo/config") {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
export const SEO_LOCALES = customConfig.SEO_LOCALES;
|
|
313
|
-
export const PRIVATE_PATH_PREFIXES = customConfig.PRIVATE_PATH_PREFIXES || [];
|
|
314
|
-
export const seoEntries = customConfig.seoEntries;
|
|
315
|
-
export const rssEntries = customConfig.rssEntries;
|
|
316
|
-
export default customConfig;`
|
|
317
|
-
}
|
|
318
|
-
return `export const SEO_LOCALES = undefined;
|
|
319
|
-
export const PRIVATE_PATH_PREFIXES = [];
|
|
378
|
+
return `export const SEO_LOCALES = ${JSON.stringify(seoLocales)};
|
|
379
|
+
export const PRIVATE_PATH_PREFIXES = ${JSON.stringify(privatePathPrefixes)};
|
|
320
380
|
export const seoEntries = async () => [];
|
|
321
|
-
export const rssEntries = undefined
|
|
381
|
+
export const rssEntries = undefined;
|
|
382
|
+
export default { SEO_LOCALES: ${JSON.stringify(seoLocales)}, PRIVATE_PATH_PREFIXES: ${JSON.stringify(privatePathPrefixes)} };`
|
|
322
383
|
}
|
|
323
384
|
if (id === "\0virtual:rimelight-seo/site-config") {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
return `export const siteConfig = { name: "Site", description: "", url: "" };`
|
|
385
|
+
return `export const siteConfig = ${JSON.stringify(normalizedSiteConfig)};
|
|
386
|
+
export default siteConfig;`
|
|
328
387
|
}
|
|
329
388
|
if (id === "\0virtual:rimelight-seo/db") {
|
|
330
389
|
if (dbPath) {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { APIRoute } from "astro"
|
|
2
|
+
import { renderDefaultOg } from "../og.js"
|
|
3
|
+
import * as siteConfigMod from "virtual:rimelight-seo/site-config"
|
|
4
|
+
import * as dbMod from "virtual:rimelight-seo/db"
|
|
5
|
+
import seoOptions from "virtual:rimelight-seo/options"
|
|
6
|
+
|
|
7
|
+
export const prerender = false
|
|
8
|
+
|
|
9
|
+
function getLocalizedText(val: unknown, locale = "en"): string {
|
|
10
|
+
if (typeof val === "object" && val !== null) {
|
|
11
|
+
const record = val as Record<string, string>
|
|
12
|
+
return record[locale] || Object.values(record)[0] || ""
|
|
13
|
+
}
|
|
14
|
+
if (typeof val === "string") return val
|
|
15
|
+
if (typeof val === "number" || typeof val === "boolean") return String(val)
|
|
16
|
+
return ""
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const GET: APIRoute = async ({ request }) => {
|
|
20
|
+
const url = new URL(request.url)
|
|
21
|
+
const routeParam = url.pathname.replace(/^\/open-graph\//, "").replace(/\.png$/, "")
|
|
22
|
+
|
|
23
|
+
const siteConfig = siteConfigMod.siteConfig || {}
|
|
24
|
+
const ogOpts = typeof seoOptions?.og === "object" ? seoOptions.og : {}
|
|
25
|
+
|
|
26
|
+
// Brand: explicit og.brand > seo.name > site.name > hostname
|
|
27
|
+
const brand = ogOpts.brand || (seoOptions as any)?.name || siteConfig.name || url.hostname
|
|
28
|
+
|
|
29
|
+
const defaultBg = ogOpts.defaultBackground ?? "dark"
|
|
30
|
+
const docsBg = ogOpts.docsBackground ?? "gradient"
|
|
31
|
+
|
|
32
|
+
let title = url.searchParams.get("title") || ""
|
|
33
|
+
let description = url.searchParams.get("description") || ""
|
|
34
|
+
let date = url.searchParams.get("pubDate") || ""
|
|
35
|
+
const badge =
|
|
36
|
+
url.searchParams.get("type") ||
|
|
37
|
+
(routeParam.includes("blog") ? "Blog Post" : routeParam.includes("legal") ? "Legal" : "")
|
|
38
|
+
|
|
39
|
+
// DB slug lookup -- same pattern as llms.txt.ts, gracefully skipped when db is absent
|
|
40
|
+
if (!title && routeParam && routeParam !== "page" && routeParam !== "default" && dbMod.db) {
|
|
41
|
+
try {
|
|
42
|
+
const { pages: pagesTable } = await import("#db/schema").catch(() => ({ pages: null }))
|
|
43
|
+
const { eq, and, isNull } = await import("drizzle-orm")
|
|
44
|
+
|
|
45
|
+
if (pagesTable) {
|
|
46
|
+
const slug = routeParam.split("/").pop() ?? ""
|
|
47
|
+
const page = await dbMod.db
|
|
48
|
+
.select({
|
|
49
|
+
title: pagesTable.title,
|
|
50
|
+
description: pagesTable.description,
|
|
51
|
+
postedAt: pagesTable.postedAt
|
|
52
|
+
})
|
|
53
|
+
.from(pagesTable)
|
|
54
|
+
.where(and(eq(pagesTable.slug, slug), isNull(pagesTable.deletedAt)))
|
|
55
|
+
.limit(1)
|
|
56
|
+
.then((r: any[]) => r[0])
|
|
57
|
+
.catch(() => undefined)
|
|
58
|
+
|
|
59
|
+
if (page) {
|
|
60
|
+
title = getLocalizedText(page.title)
|
|
61
|
+
description = description || getLocalizedText(page.description)
|
|
62
|
+
if (page.postedAt && !date) date = new Date(page.postedAt).toLocaleDateString()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// DB unavailable -- fall through to query-param values
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const isDocs = url.searchParams.get("isDocs") === "true" || routeParam.includes("docs")
|
|
71
|
+
|
|
72
|
+
return renderDefaultOg({
|
|
73
|
+
title: title || brand,
|
|
74
|
+
description,
|
|
75
|
+
brand,
|
|
76
|
+
badge,
|
|
77
|
+
date,
|
|
78
|
+
background: isDocs ? docsBg : defaultBg,
|
|
79
|
+
preview: url.searchParams.get("preview") === "1"
|
|
80
|
+
})
|
|
81
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { APIRoute } from "astro"
|
|
2
|
+
import { buildSitemapIndexXml } from "../sitemap.js"
|
|
3
|
+
import * as siteConfigMod from "virtual:rimelight-seo/site-config"
|
|
4
|
+
import seoOptions from "virtual:rimelight-seo/options"
|
|
5
|
+
|
|
6
|
+
export const GET: APIRoute = ({ site, url }) => {
|
|
7
|
+
const siteUrl = site ? site.toString() : siteConfigMod.siteConfig?.url || url.origin
|
|
8
|
+
|
|
9
|
+
const sitemapOpts = typeof seoOptions?.sitemap === "object" ? seoOptions.sitemap : {}
|
|
10
|
+
const sitemapPattern = typeof sitemapOpts.path === "string" ? sitemapOpts.path : "/sitemap.xml"
|
|
11
|
+
|
|
12
|
+
const xml = buildSitemapIndexXml([new URL(sitemapPattern, siteUrl).toString()])
|
|
13
|
+
|
|
14
|
+
return new Response(xml, {
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/xml; charset=utf-8",
|
|
17
|
+
"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400"
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -519,6 +519,47 @@ export interface AssetRouteOptions {
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
export interface RimelightSeoOptions {
|
|
522
|
+
/**
|
|
523
|
+
* Site identifier / domain (e.g. "rimelight.com")
|
|
524
|
+
*/
|
|
525
|
+
id?: string
|
|
526
|
+
/**
|
|
527
|
+
* Primary site name (e.g. "Rimelight")
|
|
528
|
+
*/
|
|
529
|
+
name?: string
|
|
530
|
+
/**
|
|
531
|
+
* Default site description
|
|
532
|
+
*/
|
|
533
|
+
description?: string
|
|
534
|
+
/**
|
|
535
|
+
* Canonical origin URL (e.g. "https://rimelight.com")
|
|
536
|
+
*/
|
|
537
|
+
url?: string
|
|
538
|
+
ogImage?: string
|
|
539
|
+
author?: string
|
|
540
|
+
email?: string
|
|
541
|
+
branding?: Partial<SiteConfig["branding"]>
|
|
542
|
+
titleTemplate?: string
|
|
543
|
+
maxDescriptionLength?: number
|
|
544
|
+
authorHandle?: string
|
|
545
|
+
openGraph?: OpenGraphMeta
|
|
546
|
+
twitter?: TwitterMeta
|
|
547
|
+
/**
|
|
548
|
+
* Full or partial SiteConfig object
|
|
549
|
+
*/
|
|
550
|
+
site?: Partial<SiteConfig>
|
|
551
|
+
/**
|
|
552
|
+
* Locale mappings for hreflang / sitemaps
|
|
553
|
+
*/
|
|
554
|
+
locales?: LocaleConfig | Record<string, string>
|
|
555
|
+
/**
|
|
556
|
+
* Path prefixes to exclude from sitemap and indexing
|
|
557
|
+
*/
|
|
558
|
+
privatePathPrefixes?: string[]
|
|
559
|
+
/**
|
|
560
|
+
* Custom SEO entry generator function
|
|
561
|
+
*/
|
|
562
|
+
entries?: () => Promise<SeoEntry[]> | SeoEntry[]
|
|
522
563
|
/**
|
|
523
564
|
* Sitemap configuration or false to disable automatic /sitemap.xml (default: true)
|
|
524
565
|
*/
|
|
@@ -558,6 +599,41 @@ export interface RimelightSeoOptions {
|
|
|
558
599
|
* icons) from a single master icon (default: true)
|
|
559
600
|
*/
|
|
560
601
|
assets?: boolean | AssetRouteOptions
|
|
602
|
+
/**
|
|
603
|
+
* Open Graph image route configuration, or false to disable automatic /open-graph/[...route].png
|
|
604
|
+
* (default: true). When enabled, the route reads brand/background from seo.name and injects a
|
|
605
|
+
* handler that supports query-param overrides and optional DB slug lookup.
|
|
606
|
+
*/
|
|
607
|
+
og?: boolean | OgRouteOptions
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Configuration for the auto-injected /open-graph/[...route].png route.
|
|
612
|
+
*/
|
|
613
|
+
export interface OgRouteOptions {
|
|
614
|
+
/**
|
|
615
|
+
* Route pattern for the OG image handler.
|
|
616
|
+
*
|
|
617
|
+
* @default "/open-graph/[...route].png"
|
|
618
|
+
*/
|
|
619
|
+
path?: string
|
|
620
|
+
/**
|
|
621
|
+
* Brand name shown top-left of every OG image. Defaults to seo.name / site.name from integration
|
|
622
|
+
* config.
|
|
623
|
+
*/
|
|
624
|
+
brand?: string
|
|
625
|
+
/**
|
|
626
|
+
* Background style used for standard pages.
|
|
627
|
+
*
|
|
628
|
+
* @default "dark"
|
|
629
|
+
*/
|
|
630
|
+
defaultBackground?: "dark" | "gradient" | (string & {})
|
|
631
|
+
/**
|
|
632
|
+
* Background style used when ?isDocs=true or the route segment contains "docs".
|
|
633
|
+
*
|
|
634
|
+
* @default "gradient"
|
|
635
|
+
*/
|
|
636
|
+
docsBackground?: "dark" | "gradient" | (string & {})
|
|
561
637
|
}
|
|
562
638
|
|
|
563
639
|
export type { LlmsPage, LlmsTxtOptions, LlmsFullTxtOptions } from "./llms.js"
|