@ingram-tech/nk-seo 0.3.0 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAuCA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAAC,IAAwB;IACtD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE,CACzC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAE3E,OAAO,SAAS,YAAY,CAAC,KAAwB;QACpD,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC;QACnD,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7D,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEjC,OAAO;YACN,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,UAAU,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE;YAC9B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,SAAS,EAAE;gBACV,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,SAAS;gBAC7B,GAAG;gBACH,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/C,GAAG,CAAC,QAAQ;oBACX,CAAC,CAAC;wBACA,MAAM,EAAE;4BACP;gCACC,GAAG,EAAE,QAAQ;gCACb,KAAK,EAAE,IAAI,CAAC,iBAAiB,IAAI,IAAI;gCACrC,MAAM,EAAE,IAAI,CAAC,kBAAkB,IAAI,GAAG;gCACtC,GAAG,EAAE,KAAK,CAAC,KAAK;6BAChB;yBACD;qBACD;oBACF,CAAC,CAAC,EAAE,CAAC;gBACN,GAAG,KAAK,CAAC,SAAS;aAClB;YACD,OAAO,EAAE;gBACR,IAAI,EAAE,qBAAqB;gBAC3B,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACvD,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3C,GAAG,KAAK,CAAC,OAAO;aAChB;SACD,CAAC;IACH,CAAC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"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"}
package/dist/og.d.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { ImageResponse } from "next/og";
2
+ type ImageResponseOptions = NonNullable<ConstructorParameters<typeof ImageResponse>[1]>;
3
+ /**
4
+ * A branded Open Graph / Twitter card built with `next/og`. Lives on its own
5
+ * entry (`@ingram-tech/nk-seo/og`) because it imports `next/og` (satori + the
6
+ * resvg wasm) — the package root and `/components` stay free of it.
7
+ *
8
+ * Satori gotcha this template encodes so you don't have to: **every element
9
+ * with more than one child must set `display: "flex"`**, and you may not mix a
10
+ * text node and an element as siblings. The layout below keeps each region a
11
+ * single text node, so titles stay plain strings and the accent is carried by
12
+ * the mark — never by a coloured `<span>` inside the headline.
13
+ */
14
+ export interface OgImageOptions {
15
+ /** The headline. Kept a single text node (no inline markup) for Satori. */
16
+ title: string;
17
+ /** Secondary line under the title. */
18
+ subtitle?: string;
19
+ /** Small label above the title (e.g. a section or category). */
20
+ eyebrow?: string;
21
+ /** Bottom-left footer, e.g. the bare domain. */
22
+ footer?: string;
23
+ /** Brand name shown next to the mark, top-left. */
24
+ wordmark?: string;
25
+ /**
26
+ * Logo image rendered as the top-left mark instead of the accent square.
27
+ * Absolute URL or data URI (Satori fetches it at render time).
28
+ */
29
+ logo?: string;
30
+ /** Accent colour — the mark, and the eyebrow if present. Default indigo. */
31
+ accent?: string;
32
+ background?: string;
33
+ /** Primary text colour. */
34
+ ink?: string;
35
+ /** Secondary text colour (subtitle, footer). */
36
+ muted?: string;
37
+ size?: {
38
+ width: number;
39
+ height: number;
40
+ };
41
+ /**
42
+ * Custom fonts, passed through to `ImageResponse` (Satori accepts raw
43
+ * TTF/OTF/WOFF data). Pair with `fontFamily` to actually use them.
44
+ */
45
+ fonts?: ImageResponseOptions["fonts"];
46
+ /** Font family for the whole card. Default "sans-serif". */
47
+ fontFamily?: string;
48
+ }
49
+ /**
50
+ * Returns an `ImageResponse` for use as the body of an `opengraph-image` /
51
+ * `twitter-image` route.
52
+ *
53
+ * @example
54
+ * // app/opengraph-image.tsx
55
+ * import { ogImageResponse } from "@ingram-tech/nk-seo/og";
56
+ * export const size = { width: 1200, height: 630 };
57
+ * export const contentType = "image/png";
58
+ * export default () =>
59
+ * ogImageResponse({
60
+ * title: "Ship faster with Acme",
61
+ * subtitle: "The all-in-one platform for modern teams.",
62
+ * wordmark: "Acme",
63
+ * footer: "example.com",
64
+ * });
65
+ */
66
+ export declare function ogImageResponse(options: OgImageOptions): ImageResponse;
67
+ export {};
68
+ //# sourceMappingURL=og.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"og.d.ts","sourceRoot":"","sources":["../src/og.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,KAAK,oBAAoB,GAAG,WAAW,CAAC,qBAAqB,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAExF;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC9B,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC;;;OAGG;IACH,KAAK,CAAC,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;IACtC,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CAiFtE"}
package/dist/og.js ADDED
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ImageResponse } from "next/og";
3
+ const DEFAULTS = {
4
+ accent: "#565ac9",
5
+ background: "#ffffff",
6
+ ink: "#171717",
7
+ muted: "#8a8a8a",
8
+ size: { width: 1200, height: 630 },
9
+ };
10
+ /**
11
+ * Returns an `ImageResponse` for use as the body of an `opengraph-image` /
12
+ * `twitter-image` route.
13
+ *
14
+ * @example
15
+ * // app/opengraph-image.tsx
16
+ * import { ogImageResponse } from "@ingram-tech/nk-seo/og";
17
+ * export const size = { width: 1200, height: 630 };
18
+ * export const contentType = "image/png";
19
+ * export default () =>
20
+ * ogImageResponse({
21
+ * title: "Ship faster with Acme",
22
+ * subtitle: "The all-in-one platform for modern teams.",
23
+ * wordmark: "Acme",
24
+ * footer: "example.com",
25
+ * });
26
+ */
27
+ export function ogImageResponse(options) {
28
+ const accent = options.accent ?? DEFAULTS.accent;
29
+ const background = options.background ?? DEFAULTS.background;
30
+ const ink = options.ink ?? DEFAULTS.ink;
31
+ const muted = options.muted ?? DEFAULTS.muted;
32
+ const size = options.size ?? DEFAULTS.size;
33
+ return new ImageResponse(_jsxs("div", { style: {
34
+ width: "100%",
35
+ height: "100%",
36
+ display: "flex",
37
+ flexDirection: "column",
38
+ justifyContent: "space-between",
39
+ background,
40
+ padding: "80px",
41
+ fontFamily: options.fontFamily ?? "sans-serif",
42
+ }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: "20px" }, children: [options.logo ? (_jsx("img", { src: options.logo, width: 44, height: 44, alt: "" })) : (_jsx("div", { style: {
43
+ width: "44px",
44
+ height: "44px",
45
+ borderRadius: "12px",
46
+ background: accent,
47
+ } })), options.wordmark ? (_jsx("div", { style: { fontSize: "40px", fontWeight: 600, color: ink }, children: options.wordmark })) : null] }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "24px" }, children: [options.eyebrow ? (_jsx("div", { style: {
48
+ fontSize: "28px",
49
+ fontWeight: 600,
50
+ letterSpacing: "0.04em",
51
+ textTransform: "uppercase",
52
+ color: accent,
53
+ }, children: options.eyebrow })) : null, _jsx("div", { style: {
54
+ fontSize: "76px",
55
+ fontWeight: 700,
56
+ lineHeight: 1.05,
57
+ letterSpacing: "-0.02em",
58
+ color: ink,
59
+ maxWidth: "1000px",
60
+ }, children: options.title }), options.subtitle ? (_jsx("div", { style: { fontSize: "34px", color: muted, maxWidth: "900px" }, children: options.subtitle })) : null] }), options.footer ? (_jsx("div", { style: { fontSize: "30px", color: muted }, children: options.footer })) : (_jsx("div", { style: { display: "flex" } }))] }), { ...size, ...(options.fonts ? { fonts: options.fonts } : {}) });
61
+ }
62
+ //# sourceMappingURL=og.js.map
package/dist/og.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"og.js","sourceRoot":"","sources":["../src/og.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import type { MetadataRoute } from "next";
2
+ /**
3
+ * Route-handler helpers for `app/sitemap.ts` and `app/robots.ts`. Pure — no
4
+ * React — so they live in the package root next to the metadata factory and can
5
+ * be imported by route handlers freely.
6
+ *
7
+ * Both resolve site-relative paths against one `baseUrl` (pass your deployment's
8
+ * own origin, e.g. `getServerUrl()`), so preview / sandbox deployments advertise
9
+ * themselves and never leak the production host.
10
+ */
11
+ type ChangeFrequency = NonNullable<MetadataRoute.Sitemap[number]["changeFrequency"]>;
12
+ /** hreflang → URL map, keyed by the locale codes Next accepts. */
13
+ export type SitemapLanguages = NonNullable<NonNullable<MetadataRoute.Sitemap[number]["alternates"]>["languages"]>;
14
+ /** A sitemap entry. A bare string is shorthand for `{ path }`. */
15
+ export interface SitemapRoute {
16
+ /** Site-relative path (e.g. "/pricing") or an absolute URL. */
17
+ path: string;
18
+ lastModified?: string | Date;
19
+ changeFrequency?: ChangeFrequency;
20
+ /** 0.0–1.0. Defaults to 1 for "/" and {@link SitemapConfig.defaultPriority} otherwise. */
21
+ priority?: number;
22
+ /**
23
+ * Per-locale alternates of this route (hreflang → site-relative path or
24
+ * absolute URL), e.g. `{ en: "/about", fr: "/fr/about" }`. Pairs with
25
+ * `<HreflangLinks>` / `hreflangAlternates` on the page side.
26
+ */
27
+ languages?: SitemapLanguages;
28
+ }
29
+ export interface SitemapConfig {
30
+ /** Absolute site origin, e.g. "https://example.com". */
31
+ baseUrl: string;
32
+ /** The indexable routes. Strings or `SitemapRoute` objects may be mixed. */
33
+ routes: Array<string | SitemapRoute>;
34
+ /** Applied when an entry omits `lastModified`. */
35
+ lastModified?: string | Date;
36
+ /** Applied when an entry omits `changeFrequency`. */
37
+ defaultChangeFrequency?: ChangeFrequency;
38
+ /** Priority for every non-root entry that omits its own. Default 0.7. */
39
+ defaultPriority?: number;
40
+ }
41
+ /**
42
+ * Builds a `MetadataRoute.Sitemap` from a list of routes, resolving relative
43
+ * paths against `baseUrl`. `"/"` defaults to priority 1; every other route
44
+ * defaults to `defaultPriority` (0.7).
45
+ *
46
+ * @example
47
+ * // app/sitemap.ts
48
+ * import { createSitemap } from "@ingram-tech/nk-seo";
49
+ * export default () =>
50
+ * createSitemap({
51
+ * baseUrl: getServerUrl(),
52
+ * routes: ["/", "/pricing", "/docs", "/faq"],
53
+ * });
54
+ */
55
+ export declare function createSitemap(config: SitemapConfig): MetadataRoute.Sitemap;
56
+ export interface RobotsConfig {
57
+ /** Absolute site origin, e.g. "https://example.com". */
58
+ baseUrl: string;
59
+ /**
60
+ * Whether this deployment is the canonical production host. When false, the
61
+ * whole site is disallowed so preview / sandbox / branch deployments never
62
+ * get indexed and dilute the real domain with duplicate content. Derive it
63
+ * from `VERCEL_ENV === "production"` (and/or a host check).
64
+ */
65
+ isProduction: boolean;
66
+ /** Prod-only disallow list (private routes, APIs, auth flows). */
67
+ disallow?: string[];
68
+ /** Prod allow rule. Default "/". */
69
+ allow?: string | string[];
70
+ /** Sitemap path advertised in prod. Default "/sitemap.xml". Pass null to omit. */
71
+ sitemapPath?: string | null;
72
+ }
73
+ /**
74
+ * Builds a `MetadataRoute.Robots`. On non-production hosts it returns a
75
+ * blanket `Disallow: /` — the single most-forgotten SEO safeguard on Vercel,
76
+ * where preview and branch URLs are otherwise crawlable and compete with the
77
+ * production domain.
78
+ *
79
+ * @example
80
+ * // app/robots.ts
81
+ * import { createRobots } from "@ingram-tech/nk-seo";
82
+ * export default () =>
83
+ * createRobots({
84
+ * baseUrl: getServerUrl(),
85
+ * isProduction: process.env.VERCEL_ENV === "production",
86
+ * disallow: ["/api/", "/internal/", "/login"],
87
+ * });
88
+ */
89
+ export declare function createRobots(config: RobotsConfig): MetadataRoute.Robots;
90
+ export {};
91
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAG1C;;;;;;;;GAQG;AAEH,KAAK,eAAe,GAAG,WAAW,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAErF,kEAAkE;AAClE,MAAM,MAAM,gBAAgB,GAAG,WAAW,CACzC,WAAW,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CACrE,CAAC;AAEF,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC5B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC7B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC;IACrC,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qDAAqD;IACrD,sBAAsB,CAAC,EAAE,eAAe,CAAC;IACzC,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAAC,OAAO,CA6B1E;AAED,MAAM,WAAW,YAAY;IAC5B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC,MAAM,CAcvE"}
package/dist/routes.js ADDED
@@ -0,0 +1,78 @@
1
+ import { absoluteUrl } from "./url.js";
2
+ const resolveLanguages = (languages, baseUrl) =>
3
+ // Object.entries/fromEntries erase the hreflang key union; the keys are
4
+ // passed through unchanged (only values are resolved), so restoring it is safe.
5
+ Object.fromEntries(Object.entries(languages).map(([locale, path]) => [
6
+ locale,
7
+ path === undefined ? path : absoluteUrl(path, baseUrl),
8
+ ]));
9
+ /**
10
+ * Builds a `MetadataRoute.Sitemap` from a list of routes, resolving relative
11
+ * paths against `baseUrl`. `"/"` defaults to priority 1; every other route
12
+ * defaults to `defaultPriority` (0.7).
13
+ *
14
+ * @example
15
+ * // app/sitemap.ts
16
+ * import { createSitemap } from "@ingram-tech/nk-seo";
17
+ * export default () =>
18
+ * createSitemap({
19
+ * baseUrl: getServerUrl(),
20
+ * routes: ["/", "/pricing", "/docs", "/faq"],
21
+ * });
22
+ */
23
+ export function createSitemap(config) {
24
+ const defaultPriority = config.defaultPriority ?? 0.7;
25
+ return config.routes.map((entry) => {
26
+ const route = typeof entry === "string" ? { path: entry } : entry;
27
+ const lastModified = route.lastModified ?? config.lastModified;
28
+ const changeFrequency = route.changeFrequency ?? config.defaultChangeFrequency;
29
+ const priority = route.priority ?? (route.path === "/" ? 1 : defaultPriority);
30
+ if (priority < 0 || priority > 1) {
31
+ throw new RangeError(`createSitemap: priority must be between 0 and 1, got ${priority} for "${route.path}"`);
32
+ }
33
+ return {
34
+ url: absoluteUrl(route.path, config.baseUrl),
35
+ ...(lastModified ? { lastModified } : {}),
36
+ ...(changeFrequency ? { changeFrequency } : {}),
37
+ priority,
38
+ ...(route.languages
39
+ ? {
40
+ alternates: {
41
+ languages: resolveLanguages(route.languages, config.baseUrl),
42
+ },
43
+ }
44
+ : {}),
45
+ };
46
+ });
47
+ }
48
+ /**
49
+ * Builds a `MetadataRoute.Robots`. On non-production hosts it returns a
50
+ * blanket `Disallow: /` — the single most-forgotten SEO safeguard on Vercel,
51
+ * where preview and branch URLs are otherwise crawlable and compete with the
52
+ * production domain.
53
+ *
54
+ * @example
55
+ * // app/robots.ts
56
+ * import { createRobots } from "@ingram-tech/nk-seo";
57
+ * export default () =>
58
+ * createRobots({
59
+ * baseUrl: getServerUrl(),
60
+ * isProduction: process.env.VERCEL_ENV === "production",
61
+ * disallow: ["/api/", "/internal/", "/login"],
62
+ * });
63
+ */
64
+ export function createRobots(config) {
65
+ if (!config.isProduction) {
66
+ return { rules: { userAgent: "*", disallow: "/" } };
67
+ }
68
+ const sitemapPath = config.sitemapPath === undefined ? "/sitemap.xml" : config.sitemapPath;
69
+ return {
70
+ rules: {
71
+ userAgent: "*",
72
+ allow: config.allow ?? "/",
73
+ ...(config.disallow?.length ? { disallow: config.disallow } : {}),
74
+ },
75
+ ...(sitemapPath ? { sitemap: absoluteUrl(sitemapPath, config.baseUrl) } : {}),
76
+ };
77
+ }
78
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
package/dist/schema.d.ts CHANGED
@@ -1,27 +1,44 @@
1
- /**
2
- * Typed schema.org JSON-LD builders. Each returns a plain object ready to hand
3
- * to `<JsonLd>` (from "@ingram-tech/nk-seo/components").
4
- *
5
- * The standalone builders take values as-is: pass **absolute** URLs for
6
- * `url`/`image`/`logo` fields. If your pages work in site-relative paths and
7
- * share one Organization, prefer {@link createSeo}, which resolves paths against
8
- * a base URL and injects the publisher/provider for you.
9
- */
10
- /** A schema.org node. Hand it to `<JsonLd data={...} />`. */
1
+ /** A loosely-typed schema.org node. The builders return more precise subtypes. */
11
2
  export type JsonLdNode = Record<string, unknown>;
3
+ /** A top-level schema.org node: the payload plus its `@context`. */
4
+ export type WithContext<T> = T & {
5
+ "@context": "https://schema.org";
6
+ };
12
7
  export interface FaqItem {
13
8
  question: string;
14
9
  answer: string;
15
10
  }
11
+ export type QuestionNode = {
12
+ "@type": "Question";
13
+ name: string;
14
+ acceptedAnswer: {
15
+ "@type": "Answer";
16
+ text: string;
17
+ };
18
+ };
19
+ export type FaqPageNode = {
20
+ "@type": "FAQPage";
21
+ mainEntity: QuestionNode[];
22
+ };
16
23
  /** FAQPage: marks up a list of question/answer pairs for FAQ rich results. */
17
- export declare function faqPage(items: FaqItem[]): JsonLdNode;
24
+ export declare function faqPage(items: FaqItem[]): WithContext<FaqPageNode>;
18
25
  export interface BreadcrumbItem {
19
26
  name: string;
20
27
  /** Absolute URL of the crumb. */
21
28
  url: string;
22
29
  }
30
+ export type ListItemNode = {
31
+ "@type": "ListItem";
32
+ position: number;
33
+ name: string;
34
+ item: string;
35
+ };
36
+ export type BreadcrumbListNode = {
37
+ "@type": "BreadcrumbList";
38
+ itemListElement: ListItemNode[];
39
+ };
23
40
  /** BreadcrumbList: the trail of links Google shows in place of the raw URL. */
24
- export declare function breadcrumbList(items: BreadcrumbItem[]): JsonLdNode;
41
+ export declare function breadcrumbList(items: BreadcrumbItem[]): WithContext<BreadcrumbListNode>;
25
42
  export interface OrganizationInput {
26
43
  name: string;
27
44
  /** Absolute URL of the organization's site. */
@@ -30,19 +47,46 @@ export interface OrganizationInput {
30
47
  logo?: string;
31
48
  description?: string;
32
49
  email?: string;
50
+ telephone?: string;
51
+ /** Registered/office postal address. */
52
+ address?: PostalAddressInput;
33
53
  /** Profile/social URLs that corroborate the entity (LinkedIn, Crunchbase…). */
34
54
  sameAs?: string[];
35
55
  }
56
+ export type PostalAddressNode = {
57
+ "@type": "PostalAddress";
58
+ } & PostalAddressInput;
59
+ export type ImageObjectNode = {
60
+ "@type": "ImageObject";
61
+ url: string;
62
+ };
63
+ export type OrganizationNode = {
64
+ "@type": "Organization";
65
+ name: string;
66
+ url?: string;
67
+ logo?: ImageObjectNode;
68
+ description?: string;
69
+ email?: string;
70
+ telephone?: string;
71
+ address?: PostalAddressNode;
72
+ sameAs?: string[];
73
+ };
36
74
  /** Organization: the entity behind the site (for the knowledge panel). */
37
- export declare function organization(input: OrganizationInput): JsonLdNode;
75
+ export declare function organization(input: OrganizationInput): WithContext<OrganizationNode>;
38
76
  export interface WebsiteInput {
39
77
  name: string;
40
78
  /** Absolute URL of the site root. */
41
79
  url: string;
42
80
  publisher?: OrganizationInput;
43
81
  }
82
+ export type WebsiteNode = {
83
+ "@type": "WebSite";
84
+ name: string;
85
+ url: string;
86
+ publisher?: OrganizationNode;
87
+ };
44
88
  /** WebSite: identifies the site as an entity; pair with {@link organization}. */
45
- export declare function website(input: WebsiteInput): JsonLdNode;
89
+ export declare function website(input: WebsiteInput): WithContext<WebsiteNode>;
46
90
  export interface OfferInput {
47
91
  priceCurrency: string;
48
92
  /** Single price. Mutually exclusive with low/high (which emit an AggregateOffer). */
@@ -53,6 +97,20 @@ export interface OfferInput {
53
97
  /** Absolute URL of the pricing/offer page. */
54
98
  url?: string;
55
99
  }
100
+ export type OfferNode = {
101
+ "@type": "Offer";
102
+ priceCurrency: string;
103
+ price?: string;
104
+ url?: string;
105
+ };
106
+ export type AggregateOfferNode = {
107
+ "@type": "AggregateOffer";
108
+ priceCurrency: string;
109
+ lowPrice?: string;
110
+ highPrice?: string;
111
+ offerCount?: string;
112
+ url?: string;
113
+ };
56
114
  export interface SoftwareApplicationInput {
57
115
  name: string;
58
116
  description?: string;
@@ -64,8 +122,17 @@ export interface SoftwareApplicationInput {
64
122
  operatingSystem?: string;
65
123
  offers?: OfferInput;
66
124
  }
125
+ export type SoftwareApplicationNode = {
126
+ "@type": "SoftwareApplication";
127
+ name: string;
128
+ applicationCategory?: string;
129
+ operatingSystem?: string;
130
+ url?: string;
131
+ description?: string;
132
+ offers?: OfferNode | AggregateOfferNode;
133
+ };
67
134
  /** SoftwareApplication: marks a product page as an app for rich results. */
68
- export declare function softwareApplication(input: SoftwareApplicationInput): JsonLdNode;
135
+ export declare function softwareApplication(input: SoftwareApplicationInput): WithContext<SoftwareApplicationNode>;
69
136
  export interface ArticleAuthor {
70
137
  name: string;
71
138
  /** Absolute URL of the author's profile/page. */
@@ -86,8 +153,26 @@ export interface ArticleInput {
86
153
  keywords?: string[];
87
154
  publisher?: OrganizationInput;
88
155
  }
156
+ export type ArticleAuthorNode = {
157
+ "@type": "Person";
158
+ name: string;
159
+ url?: string;
160
+ };
161
+ export type ArticleNode = {
162
+ "@type": "Article" | "BlogPosting" | "NewsArticle" | "ScholarlyArticle";
163
+ headline: string;
164
+ url: string;
165
+ mainEntityOfPage: string;
166
+ description?: string;
167
+ author?: ArticleAuthorNode[];
168
+ publisher?: OrganizationNode;
169
+ datePublished?: string;
170
+ dateModified?: string;
171
+ image?: string;
172
+ keywords?: string[];
173
+ };
89
174
  /** Article (or BlogPosting/NewsArticle/ScholarlyArticle) for editorial pages. */
90
- export declare function article(input: ArticleInput): JsonLdNode;
175
+ export declare function article(input: ArticleInput): WithContext<ArticleNode>;
91
176
  export interface PersonInput {
92
177
  name: string;
93
178
  /** Absolute URL of the person's page/profile. */
@@ -104,8 +189,23 @@ export interface PersonInput {
104
189
  /** Home location place name, e.g. "Brussels, Belgium". */
105
190
  homeLocation?: string;
106
191
  }
192
+ export type PersonNode = {
193
+ "@type": "Person";
194
+ name: string;
195
+ url?: string;
196
+ jobTitle?: string;
197
+ worksFor?: OrganizationNode;
198
+ image?: string;
199
+ description?: string;
200
+ email?: string;
201
+ sameAs?: string[];
202
+ homeLocation?: {
203
+ "@type": "Place";
204
+ name: string;
205
+ };
206
+ };
107
207
  /** Person: identifies an individual as an entity (portfolio, team, author bios). */
108
- export declare function person(input: PersonInput): JsonLdNode;
208
+ export declare function person(input: PersonInput): WithContext<PersonNode>;
109
209
  export interface PostalAddressInput {
110
210
  streetAddress?: string;
111
211
  addressLocality?: string;
@@ -145,8 +245,36 @@ export interface LocalBusinessInput {
145
245
  /** schema.org LocalBusiness subtype, e.g. "Store", "ArtGallery". Default "LocalBusiness". */
146
246
  type?: string;
147
247
  }
248
+ export type GeoCoordinatesNode = {
249
+ "@type": "GeoCoordinates";
250
+ latitude: number;
251
+ longitude: number;
252
+ };
253
+ export type AggregateRatingNode = {
254
+ "@type": "AggregateRating";
255
+ ratingValue: string;
256
+ reviewCount: string;
257
+ };
258
+ export type LocalBusinessNode = {
259
+ /** "LocalBusiness" or the subtype passed as `type`. */
260
+ "@type": string;
261
+ name: string;
262
+ url?: string;
263
+ description?: string;
264
+ image?: string[];
265
+ logo?: string;
266
+ email?: string;
267
+ telephone?: string;
268
+ priceRange?: string;
269
+ openingHours?: string | string[];
270
+ address?: PostalAddressNode;
271
+ geo?: GeoCoordinatesNode;
272
+ hasMap?: string;
273
+ aggregateRating?: AggregateRatingNode;
274
+ sameAs?: string[];
275
+ };
148
276
  /** LocalBusiness (or a subtype): a physical business for local/maps SEO. */
149
- export declare function localBusiness(input: LocalBusinessInput): JsonLdNode;
277
+ export declare function localBusiness(input: LocalBusinessInput): WithContext<LocalBusinessNode>;
150
278
  export interface EventLocationInput {
151
279
  name?: string;
152
280
  /** Postal address string or a URL for online events. */
@@ -174,12 +302,40 @@ export interface EventInput {
174
302
  eventStatus?: string;
175
303
  attendanceMode?: "offline" | "online" | "mixed";
176
304
  }
305
+ export type PlaceNode = {
306
+ "@type": "Place";
307
+ name?: string;
308
+ address?: string;
309
+ url?: string;
310
+ };
311
+ export type VirtualLocationNode = {
312
+ "@type": "VirtualLocation";
313
+ url?: string;
314
+ };
315
+ export type EventNode = {
316
+ "@type": "Event";
317
+ name: string;
318
+ startDate: string;
319
+ endDate: string;
320
+ eventStatus: string;
321
+ eventAttendanceMode?: string;
322
+ description?: string;
323
+ url?: string;
324
+ image?: string[];
325
+ location?: PlaceNode | VirtualLocationNode;
326
+ organizer?: OrganizationNode;
327
+ offers?: OfferNode | AggregateOfferNode;
328
+ };
177
329
  /** Event: a scheduled happening (summit, workshop, conference) for event rich results. */
178
- export declare function event(input: EventInput): JsonLdNode;
330
+ export declare function event(input: EventInput): WithContext<EventNode>;
179
331
  export interface SeoConfig {
180
- /** Absolute site origin, e.g. "https://financica.app". */
332
+ /** Absolute site origin, e.g. "https://acme.example". */
181
333
  baseUrl: string;
182
- /** Shared publisher/provider, injected into article()/website()/organization(). */
334
+ /**
335
+ * Shared publisher/provider, injected into article()/website()/organization().
336
+ * Its `url` and `logo` may be site-relative — they are resolved against
337
+ * `baseUrl`.
338
+ */
183
339
  organization?: OrganizationInput;
184
340
  }
185
341
  /** A path-aware version of a crumb: `path` is resolved against the base URL. */
@@ -190,6 +346,8 @@ export interface BreadcrumbPath {
190
346
  /**
191
347
  * Binds the builders to a site so callers pass site-relative paths instead of
192
348
  * absolute URLs, and the configured Organization is injected automatically.
349
+ * Nested URL fields (`organization.url`/`logo`, `offers.url`, article `image`)
350
+ * are resolved too — JSON-LD must never ship relative URLs.
193
351
  *
194
352
  * @example
195
353
  * const seo = createSeo({ baseUrl: getServerUrl(), organization: ORG });
@@ -201,21 +359,21 @@ export declare function createSeo(config: SeoConfig): {
201
359
  /** FAQPage (path-free). */
202
360
  faqPage: typeof faqPage;
203
361
  /** BreadcrumbList from site-relative paths. */
204
- breadcrumbs(items: BreadcrumbPath[]): JsonLdNode;
362
+ breadcrumbs(items: BreadcrumbPath[]): WithContext<BreadcrumbListNode>;
205
363
  /** Organization from config (with optional per-call overrides). */
206
- organization(overrides?: Partial<OrganizationInput>): JsonLdNode;
364
+ organization(overrides?: Partial<OrganizationInput>): WithContext<OrganizationNode>;
207
365
  /** WebSite from config, defaulting name/url/publisher to the configured org. */
208
- website(input?: Partial<WebsiteInput>): JsonLdNode;
209
- /** SoftwareApplication; `path` (default "/") is resolved to an absolute URL. */
366
+ website(input?: Partial<WebsiteInput>): WithContext<WebsiteNode>;
367
+ /** SoftwareApplication; `path` (default "/") and `offers.url` are resolved. */
210
368
  softwareApplication(input: Omit<SoftwareApplicationInput, "url"> & {
211
369
  path?: string;
212
- }): JsonLdNode;
370
+ }): WithContext<SoftwareApplicationNode>;
213
371
  /** Article; `path`/`image` are resolved and the configured org becomes publisher. */
214
372
  article(input: Omit<ArticleInput, "url" | "image" | "publisher"> & {
215
373
  path: string;
216
374
  image?: string;
217
375
  publisher?: OrganizationInput;
218
- }): JsonLdNode;
376
+ }): WithContext<ArticleNode>;
219
377
  };
220
378
  export type Seo = ReturnType<typeof createSeo>;
221
379
  //# sourceMappingURL=schema.d.ts.map