@ingram-tech/nk-seo 0.4.0 → 0.6.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.
@@ -7,6 +7,11 @@ export interface MetadataSiteConfig {
7
7
  baseUrl: string;
8
8
  /** OpenGraph `siteName`. */
9
9
  siteName: string;
10
+ /**
11
+ * `title.template` emitted by `pageMetadata.root()`, e.g. "%s | Acme".
12
+ * Applies to every child page whose title is a plain string.
13
+ */
14
+ titleTemplate?: string;
10
15
  /** Site-relative or absolute default OG/Twitter image. */
11
16
  defaultImage?: string;
12
17
  defaultImageWidth?: number;
@@ -34,21 +39,39 @@ export interface PageMetadataInput {
34
39
  /** Merged into the generated `twitter` (wins on conflict). */
35
40
  twitter?: Partial<NonNullable<Metadata["twitter"]>>;
36
41
  }
42
+ /** Inputs for `pageMetadata.root()` — the root-layout metadata. */
43
+ export interface RootMetadataInput {
44
+ /** `title.default` for pages without their own. Defaults to `siteName`. */
45
+ title?: string;
46
+ description?: string;
47
+ /** Extra `Metadata` merged over the generated object (wins on conflict). */
48
+ overrides?: Metadata;
49
+ }
37
50
  /**
38
51
  * Returns a `pageMetadata(input)` builder that produces a Next `Metadata`
39
52
  * object with a self-referencing canonical, OpenGraph, and Twitter card from
40
53
  * one title/description/path. Collapses the boilerplate every site repeats.
41
54
  *
55
+ * `pageMetadata.root(input?)` builds the root-layout metadata: `metadataBase`,
56
+ * the default title (plus `titleTemplate` when configured), and description —
57
+ * pages then only set their own title/description/path.
58
+ *
42
59
  * @example
43
- * export const metadata = createMetadata({
60
+ * export const pageMetadata = createMetadata({
44
61
  * baseUrl: "https://ingram.tech",
45
62
  * siteName: "Ingram Technologies",
63
+ * titleTemplate: "%s | Ingram Technologies",
46
64
  * defaultImage: "/images/og.png",
47
65
  * twitterSite: "@IngramTech",
48
66
  * });
67
+ * // app/layout.tsx:
68
+ * export const metadata = pageMetadata.root({ description: "AI deployment." });
49
69
  * // in a page:
50
70
  * export const metadata = pageMetadata({ title, description, path: "/services" });
51
71
  */
52
- export declare function createMetadata(site: MetadataSiteConfig): (input: PageMetadataInput) => Metadata;
72
+ export declare function createMetadata(site: MetadataSiteConfig): {
73
+ (input: PageMetadataInput): Metadata;
74
+ root(input?: RootMetadataInput): Metadata;
75
+ };
53
76
  export type PageMetadata = ReturnType<typeof createMetadata>;
54
77
  //# sourceMappingURL=metadata.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACxD,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;CACpD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB,IAIzB,OAAO,iBAAiB,KAAG,QAAQ,CA2ChE;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAGrC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACxD,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;CACpD;AAED,mEAAmE;AACnE,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB;YAGzB,iBAAiB,GAAG,QAAQ;iBA+CR,iBAAiB,GAAQ,QAAQ;EAalF;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC"}
package/dist/metadata.js CHANGED
@@ -1,30 +1,41 @@
1
+ import { absoluteUrl } from "./url.js";
1
2
  /**
2
3
  * Returns a `pageMetadata(input)` builder that produces a Next `Metadata`
3
4
  * object with a self-referencing canonical, OpenGraph, and Twitter card from
4
5
  * one title/description/path. Collapses the boilerplate every site repeats.
5
6
  *
7
+ * `pageMetadata.root(input?)` builds the root-layout metadata: `metadataBase`,
8
+ * the default title (plus `titleTemplate` when configured), and description —
9
+ * pages then only set their own title/description/path.
10
+ *
6
11
  * @example
7
- * export const metadata = createMetadata({
12
+ * export const pageMetadata = createMetadata({
8
13
  * baseUrl: "https://ingram.tech",
9
14
  * siteName: "Ingram Technologies",
15
+ * titleTemplate: "%s | Ingram Technologies",
10
16
  * defaultImage: "/images/og.png",
11
17
  * twitterSite: "@IngramTech",
12
18
  * });
19
+ * // app/layout.tsx:
20
+ * export const metadata = pageMetadata.root({ description: "AI deployment." });
13
21
  * // in a page:
14
22
  * export const metadata = pageMetadata({ title, description, path: "/services" });
15
23
  */
16
24
  export function createMetadata(site) {
17
- const absolute = (path) => /^https?:\/\//.test(path) ? path : new URL(path, site.baseUrl).toString();
18
- return function pageMetadata(input) {
25
+ const absolute = (path) => absoluteUrl(path, site.baseUrl);
26
+ function pageMetadata(input) {
19
27
  const imagePath = input.image ?? site.defaultImage;
20
28
  const imageUrl = imagePath ? absolute(imagePath) : undefined;
21
29
  const url = absolute(input.path);
22
30
  return {
31
+ metadataBase: new URL(site.baseUrl),
23
32
  title: input.title,
24
33
  description: input.description,
25
34
  ...(input.keywords ? { keywords: input.keywords } : {}),
26
35
  alternates: { canonical: url },
27
- ...(input.noIndex ? { robots: { index: false, follow: false } } : {}),
36
+ // noindex but follow: keep link equity flowing through the page. Use a
37
+ // full robots override in the page's own metadata for nofollow too.
38
+ ...(input.noIndex ? { robots: { index: false, follow: true } } : {}),
28
39
  openGraph: {
29
40
  title: input.title,
30
41
  description: input.description,
@@ -56,6 +67,18 @@ export function createMetadata(site) {
56
67
  ...input.twitter,
57
68
  },
58
69
  };
70
+ }
71
+ pageMetadata.root = function rootMetadata(input = {}) {
72
+ const title = input.title ?? site.siteName;
73
+ return {
74
+ metadataBase: new URL(site.baseUrl),
75
+ title: site.titleTemplate
76
+ ? { default: title, template: site.titleTemplate }
77
+ : title,
78
+ ...(input.description ? { description: input.description } : {}),
79
+ ...input.overrides,
80
+ };
59
81
  };
82
+ return pageMetadata;
60
83
  }
61
84
  //# sourceMappingURL=metadata.js.map
@@ -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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"}