@better-seo/next 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ Next.js integration for [**`@better-seo/core`**](../core/README.md): App Router
8
8
 
9
9
  **Peers:** `next` **>= 14.2**, `react` **>= 18.2** (see **`package.json`**).
10
10
 
11
- **Docs:** [Monorepo README](../../README.md) · [Usage](../../internal-docs/USAGE.md) · [Recipes](../../docs/recipes/)
11
+ **Docs:** [Monorepo README](../../README.md) · [Recipes](../../docs/recipes/) · [CONTRIBUTING](../../CONTRIBUTING.md)
12
12
 
13
13
  ---
14
14
 
package/dist/index.cjs CHANGED
@@ -5,17 +5,81 @@ var core = require('@better-seo/core');
5
5
  // src/register.ts
6
6
 
7
7
  // src/to-next-metadata.ts
8
- function robotsFromString(robots) {
9
- const lower = robots.toLowerCase();
10
- const noindex = lower.includes("noindex");
11
- const nofollow = lower.includes("nofollow");
12
- if (!noindex && !nofollow) {
13
- return { index: true, follow: true };
8
+ function robotsHasValueDirectives(robots) {
9
+ return robots.includes(":");
10
+ }
11
+ var SIMPLE_ROBOTS_TOKENS = /* @__PURE__ */ new Set([
12
+ "all",
13
+ "index",
14
+ "follow",
15
+ "noindex",
16
+ "nofollow",
17
+ "none",
18
+ "noarchive",
19
+ "nosnippet",
20
+ "noimageindex",
21
+ "notranslate",
22
+ "nocache",
23
+ "indexifembedded",
24
+ "nositelinkssearchbox"
25
+ ]);
26
+ function robotsFromSimpleTokenList(robots) {
27
+ const tokens = robots.split(",").map((t) => t.trim()).filter(Boolean);
28
+ const lower = tokens.map((t) => t.toLowerCase());
29
+ for (const t of lower) {
30
+ if (!SIMPLE_ROBOTS_TOKENS.has(t)) {
31
+ return robots.trim();
32
+ }
14
33
  }
15
- return {
16
- index: !noindex,
17
- follow: !nofollow
18
- };
34
+ let index = true;
35
+ let follow = true;
36
+ const out = {};
37
+ for (const t of lower) {
38
+ switch (t) {
39
+ case "noindex":
40
+ index = false;
41
+ break;
42
+ case "nofollow":
43
+ follow = false;
44
+ break;
45
+ case "none":
46
+ index = false;
47
+ follow = false;
48
+ break;
49
+ case "noarchive":
50
+ out.noarchive = true;
51
+ break;
52
+ case "nosnippet":
53
+ out.nosnippet = true;
54
+ break;
55
+ case "noimageindex":
56
+ out.noimageindex = true;
57
+ break;
58
+ case "notranslate":
59
+ out.notranslate = true;
60
+ break;
61
+ case "nocache":
62
+ out.nocache = true;
63
+ break;
64
+ case "indexifembedded":
65
+ out.indexifembedded = true;
66
+ break;
67
+ case "nositelinkssearchbox":
68
+ out.nositelinkssearchbox = true;
69
+ break;
70
+ }
71
+ }
72
+ out.index = index;
73
+ out.follow = follow;
74
+ return out;
75
+ }
76
+ function metadataRobotsFromSeoString(robots) {
77
+ const trimmed = robots.trim();
78
+ if (!trimmed) return void 0;
79
+ if (robotsHasValueDirectives(trimmed)) {
80
+ return trimmed;
81
+ }
82
+ return robotsFromSimpleTokenList(trimmed);
19
83
  }
20
84
  function omitUndefined(o) {
21
85
  return Object.fromEntries(
@@ -38,7 +102,29 @@ function toNextMetadata(seo2) {
38
102
  m.alternates = alt;
39
103
  }
40
104
  if (seo2.meta.robots) {
41
- m.robots = robotsFromString(seo2.meta.robots);
105
+ m.robots = metadataRobotsFromSeoString(seo2.meta.robots);
106
+ }
107
+ if (seo2.meta.verification) {
108
+ const v = seo2.meta.verification;
109
+ const ver = omitUndefined({
110
+ google: v.google,
111
+ yahoo: v.yahoo,
112
+ yandex: v.yandex,
113
+ me: v.me,
114
+ ...v.other && Object.keys(v.other).length > 0 ? { other: { ...v.other } } : {}
115
+ });
116
+ if (Object.keys(ver).length > 0) {
117
+ m.verification = ver;
118
+ }
119
+ }
120
+ if (seo2.meta.pagination) {
121
+ const pg = omitUndefined({
122
+ previous: seo2.meta.pagination.previous,
123
+ next: seo2.meta.pagination.next
124
+ });
125
+ if (Object.keys(pg).length > 0) {
126
+ m.pagination = pg;
127
+ }
42
128
  }
43
129
  if (seo2.openGraph) {
44
130
  const og = omitUndefined({
@@ -46,6 +132,14 @@ function toNextMetadata(seo2) {
46
132
  description: seo2.openGraph.description,
47
133
  url: seo2.openGraph.url,
48
134
  type: seo2.openGraph.type,
135
+ siteName: seo2.openGraph.siteName,
136
+ locale: seo2.openGraph.locale,
137
+ publishedTime: seo2.openGraph.publishedTime,
138
+ modifiedTime: seo2.openGraph.modifiedTime,
139
+ expirationTime: seo2.openGraph.expirationTime,
140
+ authors: seo2.openGraph.authors?.length ? [...seo2.openGraph.authors] : void 0,
141
+ section: seo2.openGraph.section,
142
+ tags: seo2.openGraph.tags?.length ? [...seo2.openGraph.tags] : void 0,
49
143
  images: seo2.openGraph.images?.map(
50
144
  (img) => omitUndefined({
51
145
  url: img.url,
@@ -53,7 +147,16 @@ function toNextMetadata(seo2) {
53
147
  height: img.height,
54
148
  alt: img.alt
55
149
  })
56
- )
150
+ ),
151
+ videos: seo2.openGraph.videos && seo2.openGraph.videos.length > 0 ? seo2.openGraph.videos.map(
152
+ (v) => omitUndefined({
153
+ url: v.url,
154
+ secureUrl: v.secureUrl,
155
+ type: v.type,
156
+ width: v.width,
157
+ height: v.height
158
+ })
159
+ ) : void 0
57
160
  });
58
161
  if (Object.keys(og).length > 0) {
59
162
  m.openGraph = og;
@@ -62,6 +165,8 @@ function toNextMetadata(seo2) {
62
165
  if (seo2.twitter) {
63
166
  const tw = omitUndefined({
64
167
  card: seo2.twitter.card,
168
+ site: seo2.twitter.site,
169
+ creator: seo2.twitter.creator,
65
170
  title: seo2.twitter.title,
66
171
  description: seo2.twitter.description,
67
172
  images: seo2.twitter.image ? [seo2.twitter.image] : void 0
@@ -91,10 +196,29 @@ function prepareNextSeo(input, config) {
91
196
  const doc = core.createSEO(input, config);
92
197
  return { metadata: toNextMetadata(doc), seo: doc };
93
198
  }
199
+ function seoRoute(route, input, config) {
200
+ const rules = config?.rules ?? [];
201
+ return toNextMetadata(core.createSEOForRoute(route, input, rules, config));
202
+ }
203
+ function prepareNextSeoForRoute(route, input, config) {
204
+ const rules = config?.rules ?? [];
205
+ const seo2 = core.createSEOForRoute(route, input, rules, config);
206
+ return { metadata: toNextMetadata(seo2), seo: seo2 };
207
+ }
208
+ function seoLayout(input, config) {
209
+ return core.createSEO(input, config);
210
+ }
211
+ function seoPage(parent, input, config) {
212
+ return withSEO(parent, input, config);
213
+ }
94
214
 
95
215
  exports.mergeNextMetadataSource = mergeNextMetadataSource;
96
216
  exports.prepareNextSeo = prepareNextSeo;
217
+ exports.prepareNextSeoForRoute = prepareNextSeoForRoute;
97
218
  exports.seo = seo;
219
+ exports.seoLayout = seoLayout;
220
+ exports.seoPage = seoPage;
221
+ exports.seoRoute = seoRoute;
98
222
  exports.toNextMetadata = toNextMetadata;
99
223
  exports.withSEO = withSEO;
100
224
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/to-next-metadata.ts","../src/register.ts","../src/surface.ts"],"names":["seo","registerAdapter","createSEO","withSeoCore","mergeSEO"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Metadata } from \"next\"\nimport type { SEO } from \"@better-seo/core\"\n\nfunction robotsFromString(robots: string): Metadata[\"robots\"] {\n const lower = robots.toLowerCase()\n const noindex = lower.includes(\"noindex\")\n const nofollow = lower.includes(\"nofollow\")\n if (!noindex && !nofollow) {\n return { index: true, follow: true }\n }\n return {\n index: !noindex,\n follow: !nofollow,\n }\n}\n\nfunction omitUndefined<T extends Record<string, unknown>>(o: T): Partial<T> {\n return Object.fromEntries(\n (Object.entries(o) as [string, unknown][]).filter(([, v]) => v !== undefined),\n ) as Partial<T>\n}\n\n/** Map normalized `SEO` → Next.js `Metadata` (App Router). JSON-LD is not included — use `@better-seo/next/json-ld`. */\nexport function toNextMetadata(seo: SEO): Metadata {\n const m: Metadata = {\n title: seo.meta.title,\n }\n if (seo.meta.description !== undefined) {\n m.description = seo.meta.description\n }\n const langs = seo.meta.alternates?.languages\n const hasLang = langs !== undefined && Object.keys(langs).length > 0\n if (seo.meta.canonical || hasLang) {\n const alt: NonNullable<Metadata[\"alternates\"]> = {}\n if (seo.meta.canonical) alt.canonical = seo.meta.canonical\n if (hasLang && langs) alt.languages = { ...langs }\n m.alternates = alt\n }\n if (seo.meta.robots) {\n m.robots = robotsFromString(seo.meta.robots)\n }\n\n if (seo.openGraph) {\n const og = omitUndefined({\n title: seo.openGraph.title,\n description: seo.openGraph.description,\n url: seo.openGraph.url,\n type: seo.openGraph.type,\n images: seo.openGraph.images?.map((img) =>\n omitUndefined({\n url: img.url,\n width: img.width,\n height: img.height,\n alt: img.alt,\n }),\n ),\n })\n if (Object.keys(og).length > 0) {\n m.openGraph = og as Metadata[\"openGraph\"]\n }\n }\n\n if (seo.twitter) {\n const tw = omitUndefined({\n card: seo.twitter.card,\n title: seo.twitter.title,\n description: seo.twitter.description,\n images: seo.twitter.image ? [seo.twitter.image] : undefined,\n })\n if (Object.keys(tw).length > 0) {\n m.twitter = tw as Metadata[\"twitter\"]\n }\n }\n\n return m\n}\n","import type { Metadata } from \"next\"\nimport { registerAdapter } from \"@better-seo/core\"\nimport { toNextMetadata } from \"./to-next-metadata.js\"\n\nregisterAdapter<Metadata>({\n id: \"next\",\n toFramework: toNextMetadata,\n})\n","import type { Metadata } from \"next\"\nimport {\n createSEO,\n mergeSEO,\n type SEO,\n type SEOConfig,\n type SEOInput,\n withSEO as withSeoCore,\n} from \"@better-seo/core\"\nimport { toNextMetadata } from \"./to-next-metadata.js\"\n\n/** Voilà: `export const metadata = seo({ title: \"...\" })` (FEATURES N1). */\nexport function seo(input: SEOInput, config?: SEOConfig): Metadata {\n return toNextMetadata(createSEO(input, config))\n}\n\n/**\n * Layer child metadata on a parent `SEO` document (layout → page), then emit Next `Metadata`.\n */\nexport function withSEO(parent: SEO, child: SEOInput, config?: SEOConfig): Metadata {\n return toNextMetadata(withSeoCore(parent, child, config))\n}\n\n/** Explicit merge without `withSEO` naming (FEATURES N2). */\nexport function mergeNextMetadataSource(\n parent: SEO,\n child: SEOInput,\n config?: SEOConfig,\n): Metadata {\n return toNextMetadata(mergeSEO(parent, child, config))\n}\n\n/** One call → Next metadata + full `SEO` (for `NextJsonLd` in the same route). */\nexport function prepareNextSeo(\n input: SEOInput,\n config?: SEOConfig,\n): { readonly metadata: Metadata; readonly seo: SEO } {\n const doc = createSEO(input, config)\n return { metadata: toNextMetadata(doc), seo: doc }\n}\n"]}
1
+ {"version":3,"sources":["../src/to-next-metadata.ts","../src/register.ts","../src/surface.ts"],"names":["seo","registerAdapter","createSEO","withSeoCore","mergeSEO","createSEOForRoute"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { Metadata } from \"next\"\nimport type { SEO } from \"@better-seo/core\"\n\n/**\n * Google / Next support comma-separated robots directives; values often use `:` (`max-snippet:20`).\n * When `:` is present, parsing is ambiguous (`unavailable_after` dates, etc.) — pass the string through\n * so Next emits the same rules as `meta name=\"robots\"`.\n */\nfunction robotsHasValueDirectives(robots: string): boolean {\n return robots.includes(\":\")\n}\n\nconst SIMPLE_ROBOTS_TOKENS = new Set([\n \"all\",\n \"index\",\n \"follow\",\n \"noindex\",\n \"nofollow\",\n \"none\",\n \"noarchive\",\n \"nosnippet\",\n \"noimageindex\",\n \"notranslate\",\n \"nocache\",\n \"indexifembedded\",\n \"nositelinkssearchbox\",\n])\n\n/**\n * Maps simple comma-separated tokens (no `:`) onto Next `Metadata.robots` object.\n * Unknown tokens fall back to raw string for safety.\n */\nfunction robotsFromSimpleTokenList(robots: string): Metadata[\"robots\"] {\n const tokens = robots\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean)\n const lower = tokens.map((t) => t.toLowerCase())\n\n for (const t of lower) {\n if (!SIMPLE_ROBOTS_TOKENS.has(t)) {\n return robots.trim()\n }\n }\n\n let index = true\n let follow = true\n const out: {\n index?: boolean\n follow?: boolean\n noarchive?: boolean\n nosnippet?: boolean\n noimageindex?: boolean\n notranslate?: boolean\n nocache?: boolean\n indexifembedded?: boolean\n nositelinkssearchbox?: boolean\n } = {}\n\n for (const t of lower) {\n switch (t) {\n case \"noindex\":\n index = false\n break\n case \"nofollow\":\n follow = false\n break\n case \"none\":\n index = false\n follow = false\n break\n case \"noarchive\":\n out.noarchive = true\n break\n case \"nosnippet\":\n out.nosnippet = true\n break\n case \"noimageindex\":\n out.noimageindex = true\n break\n case \"notranslate\":\n out.notranslate = true\n break\n case \"nocache\":\n out.nocache = true\n break\n case \"indexifembedded\":\n out.indexifembedded = true\n break\n case \"nositelinkssearchbox\":\n out.nositelinkssearchbox = true\n break\n case \"all\":\n case \"index\":\n case \"follow\":\n break\n default:\n break\n }\n }\n\n out.index = index\n out.follow = follow\n return out as Metadata[\"robots\"]\n}\n\nfunction metadataRobotsFromSeoString(robots: string): Metadata[\"robots\"] {\n const trimmed = robots.trim()\n if (!trimmed) return undefined\n\n if (robotsHasValueDirectives(trimmed)) {\n return trimmed\n }\n\n return robotsFromSimpleTokenList(trimmed)\n}\n\nfunction omitUndefined<T extends Record<string, unknown>>(o: T): Partial<T> {\n return Object.fromEntries(\n (Object.entries(o) as [string, unknown][]).filter(([, v]) => v !== undefined),\n ) as Partial<T>\n}\n\n/** Map normalized `SEO` → Next.js `Metadata` (App Router). JSON-LD is not included — use `@better-seo/next/json-ld`. */\nexport function toNextMetadata(seo: SEO): Metadata {\n const m: Metadata = {\n title: seo.meta.title,\n }\n if (seo.meta.description !== undefined) {\n m.description = seo.meta.description\n }\n const langs = seo.meta.alternates?.languages\n const hasLang = langs !== undefined && Object.keys(langs).length > 0\n if (seo.meta.canonical || hasLang) {\n const alt: NonNullable<Metadata[\"alternates\"]> = {}\n if (seo.meta.canonical) alt.canonical = seo.meta.canonical\n if (hasLang && langs) alt.languages = { ...langs }\n m.alternates = alt\n }\n if (seo.meta.robots) {\n m.robots = metadataRobotsFromSeoString(seo.meta.robots)\n }\n\n if (seo.meta.verification) {\n const v = seo.meta.verification\n const ver = omitUndefined({\n google: v.google,\n yahoo: v.yahoo,\n yandex: v.yandex,\n me: v.me,\n ...(v.other && Object.keys(v.other).length > 0 ? { other: { ...v.other } } : {}),\n })\n if (Object.keys(ver).length > 0) {\n m.verification = ver as Metadata[\"verification\"]\n }\n }\n\n if (seo.meta.pagination) {\n const pg = omitUndefined({\n previous: seo.meta.pagination.previous,\n next: seo.meta.pagination.next,\n })\n if (Object.keys(pg).length > 0) {\n m.pagination = pg as Metadata[\"pagination\"]\n }\n }\n\n if (seo.openGraph) {\n const og = omitUndefined({\n title: seo.openGraph.title,\n description: seo.openGraph.description,\n url: seo.openGraph.url,\n type: seo.openGraph.type,\n siteName: seo.openGraph.siteName,\n locale: seo.openGraph.locale,\n publishedTime: seo.openGraph.publishedTime,\n modifiedTime: seo.openGraph.modifiedTime,\n expirationTime: seo.openGraph.expirationTime,\n authors: seo.openGraph.authors?.length ? [...seo.openGraph.authors] : undefined,\n section: seo.openGraph.section,\n tags: seo.openGraph.tags?.length ? [...seo.openGraph.tags] : undefined,\n images: seo.openGraph.images?.map((img) =>\n omitUndefined({\n url: img.url,\n width: img.width,\n height: img.height,\n alt: img.alt,\n }),\n ),\n videos:\n seo.openGraph.videos && seo.openGraph.videos.length > 0\n ? seo.openGraph.videos.map((v) =>\n omitUndefined({\n url: v.url,\n secureUrl: v.secureUrl,\n type: v.type,\n width: v.width,\n height: v.height,\n }),\n )\n : undefined,\n })\n if (Object.keys(og).length > 0) {\n m.openGraph = og as Metadata[\"openGraph\"]\n }\n }\n\n if (seo.twitter) {\n const tw = omitUndefined({\n card: seo.twitter.card,\n site: seo.twitter.site,\n creator: seo.twitter.creator,\n title: seo.twitter.title,\n description: seo.twitter.description,\n images: seo.twitter.image ? [seo.twitter.image] : undefined,\n })\n if (Object.keys(tw).length > 0) {\n m.twitter = tw as Metadata[\"twitter\"]\n }\n }\n\n return m\n}\n","import type { Metadata } from \"next\"\nimport { registerAdapter } from \"@better-seo/core\"\nimport { toNextMetadata } from \"./to-next-metadata.js\"\n\nregisterAdapter<Metadata>({\n id: \"next\",\n toFramework: toNextMetadata,\n})\n","import type { Metadata } from \"next\"\nimport {\n createSEO,\n createSEOForRoute,\n mergeSEO,\n type SEO,\n type SEOConfig,\n type SEOInput,\n withSEO as withSeoCore,\n} from \"@better-seo/core\"\nimport { toNextMetadata } from \"./to-next-metadata.js\"\n\n/** Voilà: `export const metadata = seo({ title: \"...\" })` (FEATURES N1). */\nexport function seo(input: SEOInput, config?: SEOConfig): Metadata {\n return toNextMetadata(createSEO(input, config))\n}\n\n/**\n * Layer child metadata on a parent `SEO` document (layout → page), then emit Next `Metadata`.\n */\nexport function withSEO(parent: SEO, child: SEOInput, config?: SEOConfig): Metadata {\n return toNextMetadata(withSeoCore(parent, child, config))\n}\n\n/** Explicit merge without `withSEO` naming (FEATURES N2). */\nexport function mergeNextMetadataSource(\n parent: SEO,\n child: SEOInput,\n config?: SEOConfig,\n): Metadata {\n return toNextMetadata(mergeSEO(parent, child, config))\n}\n\n/** One call → Next metadata + full `SEO` (for `NextJsonLd` in the same route). */\nexport function prepareNextSeo(\n input: SEOInput,\n config?: SEOConfig,\n): { readonly metadata: Metadata; readonly seo: SEO } {\n const doc = createSEO(input, config)\n return { metadata: toNextMetadata(doc), seo: doc }\n}\n\n/** V5 — Next `Metadata` with {@link SEOConfig.rules} applied for an explicit pathname (N9). */\nexport function seoRoute(route: string, input: SEOInput, config?: SEOConfig): Metadata {\n const rules = config?.rules ?? []\n return toNextMetadata(createSEOForRoute(route, input, rules, config))\n}\n\n/** Like {@link prepareNextSeo}, but merges route rules from `config.rules` first. */\nexport function prepareNextSeoForRoute(\n route: string,\n input: SEOInput,\n config?: SEOConfig,\n): { readonly metadata: Metadata; readonly seo: SEO } {\n const rules = config?.rules ?? []\n const seo = createSEOForRoute(route, input, rules, config)\n return { metadata: toNextMetadata(seo), seo }\n}\n\n/** V4 — layout defaults as canonical parent `SEO` (alias of `createSEO` for voilà naming). */\nexport function seoLayout(input: SEOInput, config?: SEOConfig): SEO {\n return createSEO(input, config)\n}\n\n/** V4 — page `Metadata` layered on layout `SEO` (same as {@link withSEO}). */\nexport function seoPage(parent: SEO, input: SEOInput, config?: SEOConfig): Metadata {\n return withSEO(parent, input, config)\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -14,8 +14,19 @@ declare function prepareNextSeo(input: SEOInput, config?: SEOConfig): {
14
14
  readonly metadata: Metadata;
15
15
  readonly seo: SEO;
16
16
  };
17
+ /** V5 — Next `Metadata` with {@link SEOConfig.rules} applied for an explicit pathname (N9). */
18
+ declare function seoRoute(route: string, input: SEOInput, config?: SEOConfig): Metadata;
19
+ /** Like {@link prepareNextSeo}, but merges route rules from `config.rules` first. */
20
+ declare function prepareNextSeoForRoute(route: string, input: SEOInput, config?: SEOConfig): {
21
+ readonly metadata: Metadata;
22
+ readonly seo: SEO;
23
+ };
24
+ /** V4 — layout defaults as canonical parent `SEO` (alias of `createSEO` for voilà naming). */
25
+ declare function seoLayout(input: SEOInput, config?: SEOConfig): SEO;
26
+ /** V4 — page `Metadata` layered on layout `SEO` (same as {@link withSEO}). */
27
+ declare function seoPage(parent: SEO, input: SEOInput, config?: SEOConfig): Metadata;
17
28
 
18
29
  /** Map normalized `SEO` → Next.js `Metadata` (App Router). JSON-LD is not included — use `@better-seo/next/json-ld`. */
19
30
  declare function toNextMetadata(seo: SEO): Metadata;
20
31
 
21
- export { mergeNextMetadataSource, prepareNextSeo, seo, toNextMetadata, withSEO };
32
+ export { mergeNextMetadataSource, prepareNextSeo, prepareNextSeoForRoute, seo, seoLayout, seoPage, seoRoute, toNextMetadata, withSEO };
package/dist/index.d.ts CHANGED
@@ -14,8 +14,19 @@ declare function prepareNextSeo(input: SEOInput, config?: SEOConfig): {
14
14
  readonly metadata: Metadata;
15
15
  readonly seo: SEO;
16
16
  };
17
+ /** V5 — Next `Metadata` with {@link SEOConfig.rules} applied for an explicit pathname (N9). */
18
+ declare function seoRoute(route: string, input: SEOInput, config?: SEOConfig): Metadata;
19
+ /** Like {@link prepareNextSeo}, but merges route rules from `config.rules` first. */
20
+ declare function prepareNextSeoForRoute(route: string, input: SEOInput, config?: SEOConfig): {
21
+ readonly metadata: Metadata;
22
+ readonly seo: SEO;
23
+ };
24
+ /** V4 — layout defaults as canonical parent `SEO` (alias of `createSEO` for voilà naming). */
25
+ declare function seoLayout(input: SEOInput, config?: SEOConfig): SEO;
26
+ /** V4 — page `Metadata` layered on layout `SEO` (same as {@link withSEO}). */
27
+ declare function seoPage(parent: SEO, input: SEOInput, config?: SEOConfig): Metadata;
17
28
 
18
29
  /** Map normalized `SEO` → Next.js `Metadata` (App Router). JSON-LD is not included — use `@better-seo/next/json-ld`. */
19
30
  declare function toNextMetadata(seo: SEO): Metadata;
20
31
 
21
- export { mergeNextMetadataSource, prepareNextSeo, seo, toNextMetadata, withSEO };
32
+ export { mergeNextMetadataSource, prepareNextSeo, prepareNextSeoForRoute, seo, seoLayout, seoPage, seoRoute, toNextMetadata, withSEO };
package/dist/index.js CHANGED
@@ -1,19 +1,83 @@
1
- import { registerAdapter, createSEO, withSEO as withSEO$1, mergeSEO } from '@better-seo/core';
1
+ import { registerAdapter, createSEO, withSEO as withSEO$1, mergeSEO, createSEOForRoute } from '@better-seo/core';
2
2
 
3
3
  // src/register.ts
4
4
 
5
5
  // src/to-next-metadata.ts
6
- function robotsFromString(robots) {
7
- const lower = robots.toLowerCase();
8
- const noindex = lower.includes("noindex");
9
- const nofollow = lower.includes("nofollow");
10
- if (!noindex && !nofollow) {
11
- return { index: true, follow: true };
6
+ function robotsHasValueDirectives(robots) {
7
+ return robots.includes(":");
8
+ }
9
+ var SIMPLE_ROBOTS_TOKENS = /* @__PURE__ */ new Set([
10
+ "all",
11
+ "index",
12
+ "follow",
13
+ "noindex",
14
+ "nofollow",
15
+ "none",
16
+ "noarchive",
17
+ "nosnippet",
18
+ "noimageindex",
19
+ "notranslate",
20
+ "nocache",
21
+ "indexifembedded",
22
+ "nositelinkssearchbox"
23
+ ]);
24
+ function robotsFromSimpleTokenList(robots) {
25
+ const tokens = robots.split(",").map((t) => t.trim()).filter(Boolean);
26
+ const lower = tokens.map((t) => t.toLowerCase());
27
+ for (const t of lower) {
28
+ if (!SIMPLE_ROBOTS_TOKENS.has(t)) {
29
+ return robots.trim();
30
+ }
12
31
  }
13
- return {
14
- index: !noindex,
15
- follow: !nofollow
16
- };
32
+ let index = true;
33
+ let follow = true;
34
+ const out = {};
35
+ for (const t of lower) {
36
+ switch (t) {
37
+ case "noindex":
38
+ index = false;
39
+ break;
40
+ case "nofollow":
41
+ follow = false;
42
+ break;
43
+ case "none":
44
+ index = false;
45
+ follow = false;
46
+ break;
47
+ case "noarchive":
48
+ out.noarchive = true;
49
+ break;
50
+ case "nosnippet":
51
+ out.nosnippet = true;
52
+ break;
53
+ case "noimageindex":
54
+ out.noimageindex = true;
55
+ break;
56
+ case "notranslate":
57
+ out.notranslate = true;
58
+ break;
59
+ case "nocache":
60
+ out.nocache = true;
61
+ break;
62
+ case "indexifembedded":
63
+ out.indexifembedded = true;
64
+ break;
65
+ case "nositelinkssearchbox":
66
+ out.nositelinkssearchbox = true;
67
+ break;
68
+ }
69
+ }
70
+ out.index = index;
71
+ out.follow = follow;
72
+ return out;
73
+ }
74
+ function metadataRobotsFromSeoString(robots) {
75
+ const trimmed = robots.trim();
76
+ if (!trimmed) return void 0;
77
+ if (robotsHasValueDirectives(trimmed)) {
78
+ return trimmed;
79
+ }
80
+ return robotsFromSimpleTokenList(trimmed);
17
81
  }
18
82
  function omitUndefined(o) {
19
83
  return Object.fromEntries(
@@ -36,7 +100,29 @@ function toNextMetadata(seo2) {
36
100
  m.alternates = alt;
37
101
  }
38
102
  if (seo2.meta.robots) {
39
- m.robots = robotsFromString(seo2.meta.robots);
103
+ m.robots = metadataRobotsFromSeoString(seo2.meta.robots);
104
+ }
105
+ if (seo2.meta.verification) {
106
+ const v = seo2.meta.verification;
107
+ const ver = omitUndefined({
108
+ google: v.google,
109
+ yahoo: v.yahoo,
110
+ yandex: v.yandex,
111
+ me: v.me,
112
+ ...v.other && Object.keys(v.other).length > 0 ? { other: { ...v.other } } : {}
113
+ });
114
+ if (Object.keys(ver).length > 0) {
115
+ m.verification = ver;
116
+ }
117
+ }
118
+ if (seo2.meta.pagination) {
119
+ const pg = omitUndefined({
120
+ previous: seo2.meta.pagination.previous,
121
+ next: seo2.meta.pagination.next
122
+ });
123
+ if (Object.keys(pg).length > 0) {
124
+ m.pagination = pg;
125
+ }
40
126
  }
41
127
  if (seo2.openGraph) {
42
128
  const og = omitUndefined({
@@ -44,6 +130,14 @@ function toNextMetadata(seo2) {
44
130
  description: seo2.openGraph.description,
45
131
  url: seo2.openGraph.url,
46
132
  type: seo2.openGraph.type,
133
+ siteName: seo2.openGraph.siteName,
134
+ locale: seo2.openGraph.locale,
135
+ publishedTime: seo2.openGraph.publishedTime,
136
+ modifiedTime: seo2.openGraph.modifiedTime,
137
+ expirationTime: seo2.openGraph.expirationTime,
138
+ authors: seo2.openGraph.authors?.length ? [...seo2.openGraph.authors] : void 0,
139
+ section: seo2.openGraph.section,
140
+ tags: seo2.openGraph.tags?.length ? [...seo2.openGraph.tags] : void 0,
47
141
  images: seo2.openGraph.images?.map(
48
142
  (img) => omitUndefined({
49
143
  url: img.url,
@@ -51,7 +145,16 @@ function toNextMetadata(seo2) {
51
145
  height: img.height,
52
146
  alt: img.alt
53
147
  })
54
- )
148
+ ),
149
+ videos: seo2.openGraph.videos && seo2.openGraph.videos.length > 0 ? seo2.openGraph.videos.map(
150
+ (v) => omitUndefined({
151
+ url: v.url,
152
+ secureUrl: v.secureUrl,
153
+ type: v.type,
154
+ width: v.width,
155
+ height: v.height
156
+ })
157
+ ) : void 0
55
158
  });
56
159
  if (Object.keys(og).length > 0) {
57
160
  m.openGraph = og;
@@ -60,6 +163,8 @@ function toNextMetadata(seo2) {
60
163
  if (seo2.twitter) {
61
164
  const tw = omitUndefined({
62
165
  card: seo2.twitter.card,
166
+ site: seo2.twitter.site,
167
+ creator: seo2.twitter.creator,
63
168
  title: seo2.twitter.title,
64
169
  description: seo2.twitter.description,
65
170
  images: seo2.twitter.image ? [seo2.twitter.image] : void 0
@@ -89,7 +194,22 @@ function prepareNextSeo(input, config) {
89
194
  const doc = createSEO(input, config);
90
195
  return { metadata: toNextMetadata(doc), seo: doc };
91
196
  }
197
+ function seoRoute(route, input, config) {
198
+ const rules = config?.rules ?? [];
199
+ return toNextMetadata(createSEOForRoute(route, input, rules, config));
200
+ }
201
+ function prepareNextSeoForRoute(route, input, config) {
202
+ const rules = config?.rules ?? [];
203
+ const seo2 = createSEOForRoute(route, input, rules, config);
204
+ return { metadata: toNextMetadata(seo2), seo: seo2 };
205
+ }
206
+ function seoLayout(input, config) {
207
+ return createSEO(input, config);
208
+ }
209
+ function seoPage(parent, input, config) {
210
+ return withSEO(parent, input, config);
211
+ }
92
212
 
93
- export { mergeNextMetadataSource, prepareNextSeo, seo, toNextMetadata, withSEO };
213
+ export { mergeNextMetadataSource, prepareNextSeo, prepareNextSeoForRoute, seo, seoLayout, seoPage, seoRoute, toNextMetadata, withSEO };
94
214
  //# sourceMappingURL=index.js.map
95
215
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/to-next-metadata.ts","../src/register.ts","../src/surface.ts"],"names":["seo","withSeoCore"],"mappings":"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","file":"index.js","sourcesContent":["import type { Metadata } from \"next\"\nimport type { SEO } from \"@better-seo/core\"\n\nfunction robotsFromString(robots: string): Metadata[\"robots\"] {\n const lower = robots.toLowerCase()\n const noindex = lower.includes(\"noindex\")\n const nofollow = lower.includes(\"nofollow\")\n if (!noindex && !nofollow) {\n return { index: true, follow: true }\n }\n return {\n index: !noindex,\n follow: !nofollow,\n }\n}\n\nfunction omitUndefined<T extends Record<string, unknown>>(o: T): Partial<T> {\n return Object.fromEntries(\n (Object.entries(o) as [string, unknown][]).filter(([, v]) => v !== undefined),\n ) as Partial<T>\n}\n\n/** Map normalized `SEO` → Next.js `Metadata` (App Router). JSON-LD is not included — use `@better-seo/next/json-ld`. */\nexport function toNextMetadata(seo: SEO): Metadata {\n const m: Metadata = {\n title: seo.meta.title,\n }\n if (seo.meta.description !== undefined) {\n m.description = seo.meta.description\n }\n const langs = seo.meta.alternates?.languages\n const hasLang = langs !== undefined && Object.keys(langs).length > 0\n if (seo.meta.canonical || hasLang) {\n const alt: NonNullable<Metadata[\"alternates\"]> = {}\n if (seo.meta.canonical) alt.canonical = seo.meta.canonical\n if (hasLang && langs) alt.languages = { ...langs }\n m.alternates = alt\n }\n if (seo.meta.robots) {\n m.robots = robotsFromString(seo.meta.robots)\n }\n\n if (seo.openGraph) {\n const og = omitUndefined({\n title: seo.openGraph.title,\n description: seo.openGraph.description,\n url: seo.openGraph.url,\n type: seo.openGraph.type,\n images: seo.openGraph.images?.map((img) =>\n omitUndefined({\n url: img.url,\n width: img.width,\n height: img.height,\n alt: img.alt,\n }),\n ),\n })\n if (Object.keys(og).length > 0) {\n m.openGraph = og as Metadata[\"openGraph\"]\n }\n }\n\n if (seo.twitter) {\n const tw = omitUndefined({\n card: seo.twitter.card,\n title: seo.twitter.title,\n description: seo.twitter.description,\n images: seo.twitter.image ? [seo.twitter.image] : undefined,\n })\n if (Object.keys(tw).length > 0) {\n m.twitter = tw as Metadata[\"twitter\"]\n }\n }\n\n return m\n}\n","import type { Metadata } from \"next\"\nimport { registerAdapter } from \"@better-seo/core\"\nimport { toNextMetadata } from \"./to-next-metadata.js\"\n\nregisterAdapter<Metadata>({\n id: \"next\",\n toFramework: toNextMetadata,\n})\n","import type { Metadata } from \"next\"\nimport {\n createSEO,\n mergeSEO,\n type SEO,\n type SEOConfig,\n type SEOInput,\n withSEO as withSeoCore,\n} from \"@better-seo/core\"\nimport { toNextMetadata } from \"./to-next-metadata.js\"\n\n/** Voilà: `export const metadata = seo({ title: \"...\" })` (FEATURES N1). */\nexport function seo(input: SEOInput, config?: SEOConfig): Metadata {\n return toNextMetadata(createSEO(input, config))\n}\n\n/**\n * Layer child metadata on a parent `SEO` document (layout → page), then emit Next `Metadata`.\n */\nexport function withSEO(parent: SEO, child: SEOInput, config?: SEOConfig): Metadata {\n return toNextMetadata(withSeoCore(parent, child, config))\n}\n\n/** Explicit merge without `withSEO` naming (FEATURES N2). */\nexport function mergeNextMetadataSource(\n parent: SEO,\n child: SEOInput,\n config?: SEOConfig,\n): Metadata {\n return toNextMetadata(mergeSEO(parent, child, config))\n}\n\n/** One call → Next metadata + full `SEO` (for `NextJsonLd` in the same route). */\nexport function prepareNextSeo(\n input: SEOInput,\n config?: SEOConfig,\n): { readonly metadata: Metadata; readonly seo: SEO } {\n const doc = createSEO(input, config)\n return { metadata: toNextMetadata(doc), seo: doc }\n}\n"]}
1
+ {"version":3,"sources":["../src/to-next-metadata.ts","../src/register.ts","../src/surface.ts"],"names":["seo","withSeoCore"],"mappings":"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","file":"index.js","sourcesContent":["import type { Metadata } from \"next\"\nimport type { SEO } from \"@better-seo/core\"\n\n/**\n * Google / Next support comma-separated robots directives; values often use `:` (`max-snippet:20`).\n * When `:` is present, parsing is ambiguous (`unavailable_after` dates, etc.) — pass the string through\n * so Next emits the same rules as `meta name=\"robots\"`.\n */\nfunction robotsHasValueDirectives(robots: string): boolean {\n return robots.includes(\":\")\n}\n\nconst SIMPLE_ROBOTS_TOKENS = new Set([\n \"all\",\n \"index\",\n \"follow\",\n \"noindex\",\n \"nofollow\",\n \"none\",\n \"noarchive\",\n \"nosnippet\",\n \"noimageindex\",\n \"notranslate\",\n \"nocache\",\n \"indexifembedded\",\n \"nositelinkssearchbox\",\n])\n\n/**\n * Maps simple comma-separated tokens (no `:`) onto Next `Metadata.robots` object.\n * Unknown tokens fall back to raw string for safety.\n */\nfunction robotsFromSimpleTokenList(robots: string): Metadata[\"robots\"] {\n const tokens = robots\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean)\n const lower = tokens.map((t) => t.toLowerCase())\n\n for (const t of lower) {\n if (!SIMPLE_ROBOTS_TOKENS.has(t)) {\n return robots.trim()\n }\n }\n\n let index = true\n let follow = true\n const out: {\n index?: boolean\n follow?: boolean\n noarchive?: boolean\n nosnippet?: boolean\n noimageindex?: boolean\n notranslate?: boolean\n nocache?: boolean\n indexifembedded?: boolean\n nositelinkssearchbox?: boolean\n } = {}\n\n for (const t of lower) {\n switch (t) {\n case \"noindex\":\n index = false\n break\n case \"nofollow\":\n follow = false\n break\n case \"none\":\n index = false\n follow = false\n break\n case \"noarchive\":\n out.noarchive = true\n break\n case \"nosnippet\":\n out.nosnippet = true\n break\n case \"noimageindex\":\n out.noimageindex = true\n break\n case \"notranslate\":\n out.notranslate = true\n break\n case \"nocache\":\n out.nocache = true\n break\n case \"indexifembedded\":\n out.indexifembedded = true\n break\n case \"nositelinkssearchbox\":\n out.nositelinkssearchbox = true\n break\n case \"all\":\n case \"index\":\n case \"follow\":\n break\n default:\n break\n }\n }\n\n out.index = index\n out.follow = follow\n return out as Metadata[\"robots\"]\n}\n\nfunction metadataRobotsFromSeoString(robots: string): Metadata[\"robots\"] {\n const trimmed = robots.trim()\n if (!trimmed) return undefined\n\n if (robotsHasValueDirectives(trimmed)) {\n return trimmed\n }\n\n return robotsFromSimpleTokenList(trimmed)\n}\n\nfunction omitUndefined<T extends Record<string, unknown>>(o: T): Partial<T> {\n return Object.fromEntries(\n (Object.entries(o) as [string, unknown][]).filter(([, v]) => v !== undefined),\n ) as Partial<T>\n}\n\n/** Map normalized `SEO` → Next.js `Metadata` (App Router). JSON-LD is not included — use `@better-seo/next/json-ld`. */\nexport function toNextMetadata(seo: SEO): Metadata {\n const m: Metadata = {\n title: seo.meta.title,\n }\n if (seo.meta.description !== undefined) {\n m.description = seo.meta.description\n }\n const langs = seo.meta.alternates?.languages\n const hasLang = langs !== undefined && Object.keys(langs).length > 0\n if (seo.meta.canonical || hasLang) {\n const alt: NonNullable<Metadata[\"alternates\"]> = {}\n if (seo.meta.canonical) alt.canonical = seo.meta.canonical\n if (hasLang && langs) alt.languages = { ...langs }\n m.alternates = alt\n }\n if (seo.meta.robots) {\n m.robots = metadataRobotsFromSeoString(seo.meta.robots)\n }\n\n if (seo.meta.verification) {\n const v = seo.meta.verification\n const ver = omitUndefined({\n google: v.google,\n yahoo: v.yahoo,\n yandex: v.yandex,\n me: v.me,\n ...(v.other && Object.keys(v.other).length > 0 ? { other: { ...v.other } } : {}),\n })\n if (Object.keys(ver).length > 0) {\n m.verification = ver as Metadata[\"verification\"]\n }\n }\n\n if (seo.meta.pagination) {\n const pg = omitUndefined({\n previous: seo.meta.pagination.previous,\n next: seo.meta.pagination.next,\n })\n if (Object.keys(pg).length > 0) {\n m.pagination = pg as Metadata[\"pagination\"]\n }\n }\n\n if (seo.openGraph) {\n const og = omitUndefined({\n title: seo.openGraph.title,\n description: seo.openGraph.description,\n url: seo.openGraph.url,\n type: seo.openGraph.type,\n siteName: seo.openGraph.siteName,\n locale: seo.openGraph.locale,\n publishedTime: seo.openGraph.publishedTime,\n modifiedTime: seo.openGraph.modifiedTime,\n expirationTime: seo.openGraph.expirationTime,\n authors: seo.openGraph.authors?.length ? [...seo.openGraph.authors] : undefined,\n section: seo.openGraph.section,\n tags: seo.openGraph.tags?.length ? [...seo.openGraph.tags] : undefined,\n images: seo.openGraph.images?.map((img) =>\n omitUndefined({\n url: img.url,\n width: img.width,\n height: img.height,\n alt: img.alt,\n }),\n ),\n videos:\n seo.openGraph.videos && seo.openGraph.videos.length > 0\n ? seo.openGraph.videos.map((v) =>\n omitUndefined({\n url: v.url,\n secureUrl: v.secureUrl,\n type: v.type,\n width: v.width,\n height: v.height,\n }),\n )\n : undefined,\n })\n if (Object.keys(og).length > 0) {\n m.openGraph = og as Metadata[\"openGraph\"]\n }\n }\n\n if (seo.twitter) {\n const tw = omitUndefined({\n card: seo.twitter.card,\n site: seo.twitter.site,\n creator: seo.twitter.creator,\n title: seo.twitter.title,\n description: seo.twitter.description,\n images: seo.twitter.image ? [seo.twitter.image] : undefined,\n })\n if (Object.keys(tw).length > 0) {\n m.twitter = tw as Metadata[\"twitter\"]\n }\n }\n\n return m\n}\n","import type { Metadata } from \"next\"\nimport { registerAdapter } from \"@better-seo/core\"\nimport { toNextMetadata } from \"./to-next-metadata.js\"\n\nregisterAdapter<Metadata>({\n id: \"next\",\n toFramework: toNextMetadata,\n})\n","import type { Metadata } from \"next\"\nimport {\n createSEO,\n createSEOForRoute,\n mergeSEO,\n type SEO,\n type SEOConfig,\n type SEOInput,\n withSEO as withSeoCore,\n} from \"@better-seo/core\"\nimport { toNextMetadata } from \"./to-next-metadata.js\"\n\n/** Voilà: `export const metadata = seo({ title: \"...\" })` (FEATURES N1). */\nexport function seo(input: SEOInput, config?: SEOConfig): Metadata {\n return toNextMetadata(createSEO(input, config))\n}\n\n/**\n * Layer child metadata on a parent `SEO` document (layout → page), then emit Next `Metadata`.\n */\nexport function withSEO(parent: SEO, child: SEOInput, config?: SEOConfig): Metadata {\n return toNextMetadata(withSeoCore(parent, child, config))\n}\n\n/** Explicit merge without `withSEO` naming (FEATURES N2). */\nexport function mergeNextMetadataSource(\n parent: SEO,\n child: SEOInput,\n config?: SEOConfig,\n): Metadata {\n return toNextMetadata(mergeSEO(parent, child, config))\n}\n\n/** One call → Next metadata + full `SEO` (for `NextJsonLd` in the same route). */\nexport function prepareNextSeo(\n input: SEOInput,\n config?: SEOConfig,\n): { readonly metadata: Metadata; readonly seo: SEO } {\n const doc = createSEO(input, config)\n return { metadata: toNextMetadata(doc), seo: doc }\n}\n\n/** V5 — Next `Metadata` with {@link SEOConfig.rules} applied for an explicit pathname (N9). */\nexport function seoRoute(route: string, input: SEOInput, config?: SEOConfig): Metadata {\n const rules = config?.rules ?? []\n return toNextMetadata(createSEOForRoute(route, input, rules, config))\n}\n\n/** Like {@link prepareNextSeo}, but merges route rules from `config.rules` first. */\nexport function prepareNextSeoForRoute(\n route: string,\n input: SEOInput,\n config?: SEOConfig,\n): { readonly metadata: Metadata; readonly seo: SEO } {\n const rules = config?.rules ?? []\n const seo = createSEOForRoute(route, input, rules, config)\n return { metadata: toNextMetadata(seo), seo }\n}\n\n/** V4 — layout defaults as canonical parent `SEO` (alias of `createSEO` for voilà naming). */\nexport function seoLayout(input: SEOInput, config?: SEOConfig): SEO {\n return createSEO(input, config)\n}\n\n/** V4 — page `Metadata` layered on layout `SEO` (same as {@link withSEO}). */\nexport function seoPage(parent: SEO, input: SEOInput, config?: SEOConfig): Metadata {\n return withSEO(parent, input, config)\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-seo/next",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Next.js integration for @better-seo/core on the App Router (and documented Pages Router patterns): turn your SEO model into Metadata and generateMetadata, use shorthand helpers like seo() and prepareNextSeo, merge layout and page SEO without duplicating logic, and stream JSON-LD through NextJsonLd from @better-seo/next/json-ld so structured data uses the same serializeJSONLD path as everywhere else. Peer dependencies: next >= 14.2 and react >= 18.2.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "dist"
25
25
  ],
26
26
  "dependencies": {
27
- "@better-seo/core": "0.0.1"
27
+ "@better-seo/core": "0.0.2"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "next": ">=14.2.0",
@@ -36,24 +36,26 @@
36
36
  }
37
37
  },
38
38
  "scripts": {
39
- "build": "tsup",
40
- "test": "vitest run",
41
- "test:coverage": "vitest run --coverage",
42
- "lint": "eslint .",
43
- "typecheck": "tsc -p tsconfig.json --noEmit",
39
+ "build": "node ../../scripts/run-tsup.mjs",
40
+ "test": "npm exec -- vitest run",
41
+ "test:coverage": "npm exec -- vitest run --coverage",
42
+ "lint": "npm exec -- eslint .",
43
+ "typecheck": "npm exec -- tsc -p tsconfig.json --noEmit",
44
44
  "audit": "npm audit --audit-level=high"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  },
49
49
  "engines": {
50
- "node": ">=20"
50
+ "node": ">=24.0.0"
51
51
  },
52
52
  "devDependencies": {
53
- "@types/react": "^19.0.8",
54
- "@vitest/coverage-v8": "^3.0.6",
55
- "tsup": "^8.3.6",
56
- "typescript": "^5.7.3",
57
- "vitest": "^3.0.6"
53
+ "@types/react": "19.2.14",
54
+ "@vitest/coverage-v8": "4.1.2",
55
+ "next": "16.2.2",
56
+ "react": "19.2.4",
57
+ "tsup": "8.5.1",
58
+ "typescript": "6.0.2",
59
+ "vitest": "4.1.2"
58
60
  }
59
61
  }