@better-seo/next 0.0.1
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 +65 -0
- package/dist/index.cjs +101 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/json-ld.cjs +19 -0
- package/dist/json-ld.cjs.map +1 -0
- package/dist/json-ld.d.cts +8 -0
- package/dist/json-ld.d.ts +8 -0
- package/dist/json-ld.js +17 -0
- package/dist/json-ld.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# `@better-seo/next`
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@better-seo/next)
|
|
4
|
+
[](../../LICENSE)
|
|
5
|
+
[](https://github.com/0xMilord/better-seo-js/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
Next.js integration for [**`@better-seo/core`**](../core/README.md): App Router **`Metadata`**, **`generateMetadata`** patterns, shorthand **`seo()`**, **`prepareNextSeo`**, layout merge via **`withSEO`**, and **`NextJsonLd`** so JSON-LD uses the same **`serializeJSONLD`** path as the rest of the stack.
|
|
8
|
+
|
|
9
|
+
**Peers:** `next` **>= 14.2**, `react` **>= 18.2** (see **`package.json`**).
|
|
10
|
+
|
|
11
|
+
**Docs:** [Monorepo README](../../README.md) · [Usage](../../internal-docs/USAGE.md) · [Recipes](../../docs/recipes/)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @better-seo/core @better-seo/next
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Entry points
|
|
24
|
+
|
|
25
|
+
| Import | Role |
|
|
26
|
+
| ------------------------------ | --------------------------------------------------------------------------------------- |
|
|
27
|
+
| **`@better-seo/next`** | `seo`, `prepareNextSeo`, `withSEO`, `toNextMetadata`, adapter registration for `"next"` |
|
|
28
|
+
| **`@better-seo/next/json-ld`** | **`NextJsonLd`** — renders `<script type="application/ld+json">` from a **`SEO`** |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Scripts (monorepo)
|
|
33
|
+
|
|
34
|
+
From **`packages/next`**:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm run build
|
|
38
|
+
npm run test
|
|
39
|
+
npm run test:coverage
|
|
40
|
+
npm run lint
|
|
41
|
+
npm run typecheck
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**E2E:** Playwright tests in **`examples/nextjs-app`** exercise real HTML head output (runs in root **`npm run ci`**).
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Coverage
|
|
49
|
+
|
|
50
|
+
[`vitest.config.ts`](./vitest.config.ts) scopes coverage to **`src/to-next-metadata.ts`** (mapping logic); wiring in `surface` / `register` is covered by E2E.
|
|
51
|
+
|
|
52
|
+
| Metric | Minimum |
|
|
53
|
+
| -------------- | ------- |
|
|
54
|
+
| **Lines** | 82% |
|
|
55
|
+
| **Statements** | 82% |
|
|
56
|
+
| **Functions** | 80% |
|
|
57
|
+
| **Branches** | 72% |
|
|
58
|
+
|
|
59
|
+
LCov: **`coverage/lcov.info`** (uploaded from CI when present).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT — see [**LICENSE**](../../LICENSE).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@better-seo/core');
|
|
4
|
+
|
|
5
|
+
// src/register.ts
|
|
6
|
+
|
|
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 };
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
index: !noindex,
|
|
17
|
+
follow: !nofollow
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function omitUndefined(o) {
|
|
21
|
+
return Object.fromEntries(
|
|
22
|
+
Object.entries(o).filter(([, v]) => v !== void 0)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
function toNextMetadata(seo2) {
|
|
26
|
+
const m = {
|
|
27
|
+
title: seo2.meta.title
|
|
28
|
+
};
|
|
29
|
+
if (seo2.meta.description !== void 0) {
|
|
30
|
+
m.description = seo2.meta.description;
|
|
31
|
+
}
|
|
32
|
+
const langs = seo2.meta.alternates?.languages;
|
|
33
|
+
const hasLang = langs !== void 0 && Object.keys(langs).length > 0;
|
|
34
|
+
if (seo2.meta.canonical || hasLang) {
|
|
35
|
+
const alt = {};
|
|
36
|
+
if (seo2.meta.canonical) alt.canonical = seo2.meta.canonical;
|
|
37
|
+
if (hasLang && langs) alt.languages = { ...langs };
|
|
38
|
+
m.alternates = alt;
|
|
39
|
+
}
|
|
40
|
+
if (seo2.meta.robots) {
|
|
41
|
+
m.robots = robotsFromString(seo2.meta.robots);
|
|
42
|
+
}
|
|
43
|
+
if (seo2.openGraph) {
|
|
44
|
+
const og = omitUndefined({
|
|
45
|
+
title: seo2.openGraph.title,
|
|
46
|
+
description: seo2.openGraph.description,
|
|
47
|
+
url: seo2.openGraph.url,
|
|
48
|
+
type: seo2.openGraph.type,
|
|
49
|
+
images: seo2.openGraph.images?.map(
|
|
50
|
+
(img) => omitUndefined({
|
|
51
|
+
url: img.url,
|
|
52
|
+
width: img.width,
|
|
53
|
+
height: img.height,
|
|
54
|
+
alt: img.alt
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
});
|
|
58
|
+
if (Object.keys(og).length > 0) {
|
|
59
|
+
m.openGraph = og;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (seo2.twitter) {
|
|
63
|
+
const tw = omitUndefined({
|
|
64
|
+
card: seo2.twitter.card,
|
|
65
|
+
title: seo2.twitter.title,
|
|
66
|
+
description: seo2.twitter.description,
|
|
67
|
+
images: seo2.twitter.image ? [seo2.twitter.image] : void 0
|
|
68
|
+
});
|
|
69
|
+
if (Object.keys(tw).length > 0) {
|
|
70
|
+
m.twitter = tw;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return m;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/register.ts
|
|
77
|
+
core.registerAdapter({
|
|
78
|
+
id: "next",
|
|
79
|
+
toFramework: toNextMetadata
|
|
80
|
+
});
|
|
81
|
+
function seo(input, config) {
|
|
82
|
+
return toNextMetadata(core.createSEO(input, config));
|
|
83
|
+
}
|
|
84
|
+
function withSEO(parent, child, config) {
|
|
85
|
+
return toNextMetadata(core.withSEO(parent, child, config));
|
|
86
|
+
}
|
|
87
|
+
function mergeNextMetadataSource(parent, child, config) {
|
|
88
|
+
return toNextMetadata(core.mergeSEO(parent, child, config));
|
|
89
|
+
}
|
|
90
|
+
function prepareNextSeo(input, config) {
|
|
91
|
+
const doc = core.createSEO(input, config);
|
|
92
|
+
return { metadata: toNextMetadata(doc), seo: doc };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exports.mergeNextMetadataSource = mergeNextMetadataSource;
|
|
96
|
+
exports.prepareNextSeo = prepareNextSeo;
|
|
97
|
+
exports.seo = seo;
|
|
98
|
+
exports.toNextMetadata = toNextMetadata;
|
|
99
|
+
exports.withSEO = withSEO;
|
|
100
|
+
//# sourceMappingURL=index.cjs.map
|
|
101
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Metadata } from 'next';
|
|
2
|
+
import { SEO, SEOInput, SEOConfig } from '@better-seo/core';
|
|
3
|
+
|
|
4
|
+
/** Voilà: `export const metadata = seo({ title: "..." })` (FEATURES N1). */
|
|
5
|
+
declare function seo(input: SEOInput, config?: SEOConfig): Metadata;
|
|
6
|
+
/**
|
|
7
|
+
* Layer child metadata on a parent `SEO` document (layout → page), then emit Next `Metadata`.
|
|
8
|
+
*/
|
|
9
|
+
declare function withSEO(parent: SEO, child: SEOInput, config?: SEOConfig): Metadata;
|
|
10
|
+
/** Explicit merge without `withSEO` naming (FEATURES N2). */
|
|
11
|
+
declare function mergeNextMetadataSource(parent: SEO, child: SEOInput, config?: SEOConfig): Metadata;
|
|
12
|
+
/** One call → Next metadata + full `SEO` (for `NextJsonLd` in the same route). */
|
|
13
|
+
declare function prepareNextSeo(input: SEOInput, config?: SEOConfig): {
|
|
14
|
+
readonly metadata: Metadata;
|
|
15
|
+
readonly seo: SEO;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Map normalized `SEO` → Next.js `Metadata` (App Router). JSON-LD is not included — use `@better-seo/next/json-ld`. */
|
|
19
|
+
declare function toNextMetadata(seo: SEO): Metadata;
|
|
20
|
+
|
|
21
|
+
export { mergeNextMetadataSource, prepareNextSeo, seo, toNextMetadata, withSEO };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Metadata } from 'next';
|
|
2
|
+
import { SEO, SEOInput, SEOConfig } from '@better-seo/core';
|
|
3
|
+
|
|
4
|
+
/** Voilà: `export const metadata = seo({ title: "..." })` (FEATURES N1). */
|
|
5
|
+
declare function seo(input: SEOInput, config?: SEOConfig): Metadata;
|
|
6
|
+
/**
|
|
7
|
+
* Layer child metadata on a parent `SEO` document (layout → page), then emit Next `Metadata`.
|
|
8
|
+
*/
|
|
9
|
+
declare function withSEO(parent: SEO, child: SEOInput, config?: SEOConfig): Metadata;
|
|
10
|
+
/** Explicit merge without `withSEO` naming (FEATURES N2). */
|
|
11
|
+
declare function mergeNextMetadataSource(parent: SEO, child: SEOInput, config?: SEOConfig): Metadata;
|
|
12
|
+
/** One call → Next metadata + full `SEO` (for `NextJsonLd` in the same route). */
|
|
13
|
+
declare function prepareNextSeo(input: SEOInput, config?: SEOConfig): {
|
|
14
|
+
readonly metadata: Metadata;
|
|
15
|
+
readonly seo: SEO;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Map normalized `SEO` → Next.js `Metadata` (App Router). JSON-LD is not included — use `@better-seo/next/json-ld`. */
|
|
19
|
+
declare function toNextMetadata(seo: SEO): Metadata;
|
|
20
|
+
|
|
21
|
+
export { mergeNextMetadataSource, prepareNextSeo, seo, toNextMetadata, withSEO };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { registerAdapter, createSEO, withSEO as withSEO$1, mergeSEO } from '@better-seo/core';
|
|
2
|
+
|
|
3
|
+
// src/register.ts
|
|
4
|
+
|
|
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 };
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
index: !noindex,
|
|
15
|
+
follow: !nofollow
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function omitUndefined(o) {
|
|
19
|
+
return Object.fromEntries(
|
|
20
|
+
Object.entries(o).filter(([, v]) => v !== void 0)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function toNextMetadata(seo2) {
|
|
24
|
+
const m = {
|
|
25
|
+
title: seo2.meta.title
|
|
26
|
+
};
|
|
27
|
+
if (seo2.meta.description !== void 0) {
|
|
28
|
+
m.description = seo2.meta.description;
|
|
29
|
+
}
|
|
30
|
+
const langs = seo2.meta.alternates?.languages;
|
|
31
|
+
const hasLang = langs !== void 0 && Object.keys(langs).length > 0;
|
|
32
|
+
if (seo2.meta.canonical || hasLang) {
|
|
33
|
+
const alt = {};
|
|
34
|
+
if (seo2.meta.canonical) alt.canonical = seo2.meta.canonical;
|
|
35
|
+
if (hasLang && langs) alt.languages = { ...langs };
|
|
36
|
+
m.alternates = alt;
|
|
37
|
+
}
|
|
38
|
+
if (seo2.meta.robots) {
|
|
39
|
+
m.robots = robotsFromString(seo2.meta.robots);
|
|
40
|
+
}
|
|
41
|
+
if (seo2.openGraph) {
|
|
42
|
+
const og = omitUndefined({
|
|
43
|
+
title: seo2.openGraph.title,
|
|
44
|
+
description: seo2.openGraph.description,
|
|
45
|
+
url: seo2.openGraph.url,
|
|
46
|
+
type: seo2.openGraph.type,
|
|
47
|
+
images: seo2.openGraph.images?.map(
|
|
48
|
+
(img) => omitUndefined({
|
|
49
|
+
url: img.url,
|
|
50
|
+
width: img.width,
|
|
51
|
+
height: img.height,
|
|
52
|
+
alt: img.alt
|
|
53
|
+
})
|
|
54
|
+
)
|
|
55
|
+
});
|
|
56
|
+
if (Object.keys(og).length > 0) {
|
|
57
|
+
m.openGraph = og;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (seo2.twitter) {
|
|
61
|
+
const tw = omitUndefined({
|
|
62
|
+
card: seo2.twitter.card,
|
|
63
|
+
title: seo2.twitter.title,
|
|
64
|
+
description: seo2.twitter.description,
|
|
65
|
+
images: seo2.twitter.image ? [seo2.twitter.image] : void 0
|
|
66
|
+
});
|
|
67
|
+
if (Object.keys(tw).length > 0) {
|
|
68
|
+
m.twitter = tw;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return m;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/register.ts
|
|
75
|
+
registerAdapter({
|
|
76
|
+
id: "next",
|
|
77
|
+
toFramework: toNextMetadata
|
|
78
|
+
});
|
|
79
|
+
function seo(input, config) {
|
|
80
|
+
return toNextMetadata(createSEO(input, config));
|
|
81
|
+
}
|
|
82
|
+
function withSEO(parent, child, config) {
|
|
83
|
+
return toNextMetadata(withSEO$1(parent, child, config));
|
|
84
|
+
}
|
|
85
|
+
function mergeNextMetadataSource(parent, child, config) {
|
|
86
|
+
return toNextMetadata(mergeSEO(parent, child, config));
|
|
87
|
+
}
|
|
88
|
+
function prepareNextSeo(input, config) {
|
|
89
|
+
const doc = createSEO(input, config);
|
|
90
|
+
return { metadata: toNextMetadata(doc), seo: doc };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { mergeNextMetadataSource, prepareNextSeo, seo, toNextMetadata, withSEO };
|
|
94
|
+
//# sourceMappingURL=index.js.map
|
|
95
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/json-ld.cjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@better-seo/core');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/next-json-ld.tsx
|
|
7
|
+
function NextJsonLd(props) {
|
|
8
|
+
const { seo } = props;
|
|
9
|
+
if (!seo.schema.length) return null;
|
|
10
|
+
const [first] = seo.schema;
|
|
11
|
+
if (!first) return null;
|
|
12
|
+
const payload = seo.schema.length === 1 ? first : [...seo.schema];
|
|
13
|
+
const json = core.serializeJSONLD(payload);
|
|
14
|
+
return /* @__PURE__ */ jsxRuntime.jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: json } });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.NextJsonLd = NextJsonLd;
|
|
18
|
+
//# sourceMappingURL=json-ld.cjs.map
|
|
19
|
+
//# sourceMappingURL=json-ld.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next-json-ld.tsx"],"names":["serializeJSONLD","jsx"],"mappings":";;;;;;AAIO,SAAS,WAAW,KAAA,EAAwD;AACjF,EAAA,MAAM,EAAE,KAAI,GAAI,KAAA;AAChB,EAAA,IAAI,CAAC,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ,OAAO,IAAA;AAC/B,EAAA,MAAM,CAAC,KAAK,CAAA,GAAI,GAAA,CAAI,MAAA;AACpB,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,OAAA,GAAU,IAAI,MAAA,CAAO,MAAA,KAAW,IAAI,KAAA,GAAQ,CAAC,GAAG,GAAA,CAAI,MAAM,CAAA;AAChE,EAAA,MAAM,IAAA,GAAOA,qBAAgB,OAAO,CAAA;AACpC,EAAA,uBAAOC,cAAA,CAAC,YAAO,IAAA,EAAK,qBAAA,EAAsB,yBAAyB,EAAE,MAAA,EAAQ,MAAK,EAAG,CAAA;AACvF","file":"json-ld.cjs","sourcesContent":["import { serializeJSONLD } from \"@better-seo/core\"\nimport type { SEO } from \"@better-seo/core\"\n\n/** Renders JSON-LD using core `serializeJSONLD` only (FEATURES N4, ARCHITECTURE §7). */\nexport function NextJsonLd(props: { readonly seo: SEO }): React.JSX.Element | null {\n const { seo } = props\n if (!seo.schema.length) return null\n const [first] = seo.schema\n if (!first) return null\n const payload = seo.schema.length === 1 ? first : [...seo.schema]\n const json = serializeJSONLD(payload)\n return <script type=\"application/ld+json\" dangerouslySetInnerHTML={{ __html: json }} />\n}\n"]}
|
package/dist/json-ld.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { serializeJSONLD } from '@better-seo/core';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/next-json-ld.tsx
|
|
5
|
+
function NextJsonLd(props) {
|
|
6
|
+
const { seo } = props;
|
|
7
|
+
if (!seo.schema.length) return null;
|
|
8
|
+
const [first] = seo.schema;
|
|
9
|
+
if (!first) return null;
|
|
10
|
+
const payload = seo.schema.length === 1 ? first : [...seo.schema];
|
|
11
|
+
const json = serializeJSONLD(payload);
|
|
12
|
+
return /* @__PURE__ */ jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: json } });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { NextJsonLd };
|
|
16
|
+
//# sourceMappingURL=json-ld.js.map
|
|
17
|
+
//# sourceMappingURL=json-ld.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next-json-ld.tsx"],"names":[],"mappings":";;;;AAIO,SAAS,WAAW,KAAA,EAAwD;AACjF,EAAA,MAAM,EAAE,KAAI,GAAI,KAAA;AAChB,EAAA,IAAI,CAAC,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ,OAAO,IAAA;AAC/B,EAAA,MAAM,CAAC,KAAK,CAAA,GAAI,GAAA,CAAI,MAAA;AACpB,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,MAAM,OAAA,GAAU,IAAI,MAAA,CAAO,MAAA,KAAW,IAAI,KAAA,GAAQ,CAAC,GAAG,GAAA,CAAI,MAAM,CAAA;AAChE,EAAA,MAAM,IAAA,GAAO,gBAAgB,OAAO,CAAA;AACpC,EAAA,uBAAO,GAAA,CAAC,YAAO,IAAA,EAAK,qBAAA,EAAsB,yBAAyB,EAAE,MAAA,EAAQ,MAAK,EAAG,CAAA;AACvF","file":"json-ld.js","sourcesContent":["import { serializeJSONLD } from \"@better-seo/core\"\nimport type { SEO } from \"@better-seo/core\"\n\n/** Renders JSON-LD using core `serializeJSONLD` only (FEATURES N4, ARCHITECTURE §7). */\nexport function NextJsonLd(props: { readonly seo: SEO }): React.JSX.Element | null {\n const { seo } = props\n if (!seo.schema.length) return null\n const [first] = seo.schema\n if (!first) return null\n const payload = seo.schema.length === 1 ? first : [...seo.schema]\n const json = serializeJSONLD(payload)\n return <script type=\"application/ld+json\" dangerouslySetInnerHTML={{ __html: json }} />\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@better-seo/next",
|
|
3
|
+
"version": "0.0.1",
|
|
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
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": true,
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
},
|
|
17
|
+
"./json-ld": {
|
|
18
|
+
"types": "./dist/json-ld.d.ts",
|
|
19
|
+
"import": "./dist/json-ld.js",
|
|
20
|
+
"require": "./dist/json-ld.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@better-seo/core": "0.0.1"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"next": ">=14.2.0",
|
|
31
|
+
"react": ">=18.2.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"next": {
|
|
35
|
+
"optional": false
|
|
36
|
+
}
|
|
37
|
+
},
|
|
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",
|
|
44
|
+
"audit": "npm audit --audit-level=high"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=20"
|
|
51
|
+
},
|
|
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"
|
|
58
|
+
}
|
|
59
|
+
}
|