@answerable-kit/metadata 0.1.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.
- package/README.md +45 -0
- package/dist/define-seo.d.ts +75 -0
- package/dist/define-seo.d.ts.map +1 -0
- package/dist/define-seo.js +128 -0
- package/dist/define-seo.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @answerable-kit/metadata
|
|
2
|
+
|
|
3
|
+
Next.js App Router metadata helpers for the [Answerable](https://github.com/Anuj7411/answerable) SEO toolkit. Compose `title`, `description`, canonical URL, OpenGraph, Twitter, and robots directives from a single typed input — with smart fallbacks across the social-card chain.
|
|
4
|
+
|
|
5
|
+
> **Pre-alpha.** Only `defineSeo()` ships today. More helpers (`defineRobots()`, `defineOg()`) land in Phase 1 — see the [project roadmap](../../docs/internal/ROADMAP.md).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @answerable-kit/metadata
|
|
11
|
+
# requires next >= 14
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// app/layout.tsx
|
|
18
|
+
import { defineSeo } from '@answerable-kit/metadata';
|
|
19
|
+
|
|
20
|
+
export const metadata = defineSeo({
|
|
21
|
+
title: { default: 'Acme', template: '%s — Acme' },
|
|
22
|
+
description: 'The friendliest widget shop on the internet.',
|
|
23
|
+
url: 'https://acme.com',
|
|
24
|
+
image: 'https://acme.com/og.png',
|
|
25
|
+
siteName: 'Acme',
|
|
26
|
+
locale: 'en_US',
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
What you get back is a fully-formed Next.js `Metadata` object: `title` template, `metadataBase`, `alternates.canonical`, `openGraph`, `twitter`, and (when supplied) `robots` — all populated from the same source of truth.
|
|
31
|
+
|
|
32
|
+
## Smart defaults
|
|
33
|
+
|
|
34
|
+
- **Image fallback chain**: a single top-level `image` populates both `openGraph.images` and `twitter.images` unless overridden.
|
|
35
|
+
- **Title / description fallback**: OG and Twitter inherit from the top level unless explicitly set, and Twitter inherits from OG when set there but not on Twitter.
|
|
36
|
+
- **Canonical** is required and validated as an absolute `http(s)` URL.
|
|
37
|
+
- **`twitter.card` defaults to `summary_large_image`** — the better-converting default.
|
|
38
|
+
|
|
39
|
+
## Robots conflict guard
|
|
40
|
+
|
|
41
|
+
Googlebot directives must be *at most as permissive* as the top-level directives — Google can be told to be stricter than the global, but not looser. `defineSeo()` rejects e.g. `index: false` at the top with `googleBot.index: true`.
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
[MIT](../../LICENSE) © 2026 Anuj Ojha
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
/**
|
|
3
|
+
* A static title (`"Acme"`) or a templated title with `%s` placeholder
|
|
4
|
+
* that Next.js substitutes with each child page's title
|
|
5
|
+
* (`{ default: "Acme", template: "%s — Acme" }`).
|
|
6
|
+
*/
|
|
7
|
+
export type SeoTitle = string | {
|
|
8
|
+
readonly default: string;
|
|
9
|
+
readonly template: string;
|
|
10
|
+
};
|
|
11
|
+
export interface SeoOpenGraph {
|
|
12
|
+
/** Defaults to the top-level title. */
|
|
13
|
+
readonly title?: string | undefined;
|
|
14
|
+
/** Defaults to the top-level description. */
|
|
15
|
+
readonly description?: string | undefined;
|
|
16
|
+
readonly siteName?: string | undefined;
|
|
17
|
+
/** BCP 47 locale code, e.g. `"en_US"`. */
|
|
18
|
+
readonly locale?: string | undefined;
|
|
19
|
+
readonly type?: 'website' | 'article' | 'profile' | 'book' | undefined;
|
|
20
|
+
/** Absolute http(s) URLs. Defaults to `[image]` from the top level. */
|
|
21
|
+
readonly images?: readonly string[] | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface SeoTwitter {
|
|
24
|
+
readonly card?: 'summary' | 'summary_large_image' | 'app' | 'player' | undefined;
|
|
25
|
+
/** Defaults to og.title, then top-level title. */
|
|
26
|
+
readonly title?: string | undefined;
|
|
27
|
+
/** Defaults to og.description, then top-level description. */
|
|
28
|
+
readonly description?: string | undefined;
|
|
29
|
+
/** Defaults to og.images, then `[image]` from the top level. */
|
|
30
|
+
readonly images?: readonly string[] | undefined;
|
|
31
|
+
/** Twitter handle, e.g. `"@acme"`. */
|
|
32
|
+
readonly site?: string | undefined;
|
|
33
|
+
}
|
|
34
|
+
export interface SeoGoogleBotDirectives {
|
|
35
|
+
readonly index?: boolean | undefined;
|
|
36
|
+
readonly follow?: boolean | undefined;
|
|
37
|
+
readonly noimageindex?: boolean | undefined;
|
|
38
|
+
readonly 'max-snippet'?: number | undefined;
|
|
39
|
+
readonly 'max-image-preview'?: 'none' | 'standard' | 'large' | undefined;
|
|
40
|
+
readonly 'max-video-preview'?: number | undefined;
|
|
41
|
+
}
|
|
42
|
+
export interface SeoRobots {
|
|
43
|
+
readonly index?: boolean | undefined;
|
|
44
|
+
readonly follow?: boolean | undefined;
|
|
45
|
+
readonly googleBot?: SeoGoogleBotDirectives | undefined;
|
|
46
|
+
}
|
|
47
|
+
export interface SeoInput {
|
|
48
|
+
readonly title: SeoTitle;
|
|
49
|
+
readonly description: string;
|
|
50
|
+
/** Canonical absolute http(s) URL for this page. */
|
|
51
|
+
readonly url: string;
|
|
52
|
+
/** Single hero image. Populates both OG and Twitter unless overridden. */
|
|
53
|
+
readonly image?: string | undefined;
|
|
54
|
+
/** Site name, propagated to `openGraph.siteName`. */
|
|
55
|
+
readonly siteName?: string | undefined;
|
|
56
|
+
/** Default locale, propagated to `openGraph.locale`. */
|
|
57
|
+
readonly locale?: string | undefined;
|
|
58
|
+
readonly openGraph?: SeoOpenGraph | undefined;
|
|
59
|
+
readonly twitter?: SeoTwitter | undefined;
|
|
60
|
+
readonly robots?: SeoRobots | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Compose a complete Next.js `Metadata` object from a single typed
|
|
64
|
+
* input. Drives audit checks **A1** (title length), **A3** (meta
|
|
65
|
+
* description length), **A4** (canonical URL), and the **F-series**
|
|
66
|
+
* (OpenGraph + Twitter cards).
|
|
67
|
+
*
|
|
68
|
+
* @throws SchemaValidationError batching every issue across title,
|
|
69
|
+
* description, and robots directives.
|
|
70
|
+
* @throws InvalidUrlError for the first malformed URL encountered
|
|
71
|
+
* (`url`, `image`, any `openGraph.images` entry, any
|
|
72
|
+
* `twitter.images` entry).
|
|
73
|
+
*/
|
|
74
|
+
export declare function defineSeo(input: SeoInput): Metadata;
|
|
75
|
+
//# sourceMappingURL=define-seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-seo.d.ts","sourceRoot":"","sources":["../src/define-seo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { SchemaValidationError, parseAbsoluteUrl } from '@answerable-kit/core';
|
|
2
|
+
function validateTitle(t, issues) {
|
|
3
|
+
if (typeof t === 'string') {
|
|
4
|
+
if (t.trim() === '')
|
|
5
|
+
issues.push('title is empty');
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (t.default.trim() === '')
|
|
9
|
+
issues.push('title.default is empty');
|
|
10
|
+
if (t.template.trim() === '')
|
|
11
|
+
issues.push('title.template is empty');
|
|
12
|
+
else if (!t.template.includes('%s')) {
|
|
13
|
+
issues.push('title.template must contain "%s" placeholder');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function validateRobots(r, issues) {
|
|
17
|
+
const bot = r.googleBot;
|
|
18
|
+
if (bot === undefined)
|
|
19
|
+
return;
|
|
20
|
+
// Googlebot must be at most as permissive as the top-level directive.
|
|
21
|
+
if (r.index === false && bot.index === true) {
|
|
22
|
+
issues.push('robots: googleBot.index cannot be true when top-level index is false (googleBot must be at most as permissive as the top-level directive)');
|
|
23
|
+
}
|
|
24
|
+
if (r.follow === false && bot.follow === true) {
|
|
25
|
+
issues.push('robots: googleBot.follow cannot be true when top-level follow is false');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function titleText(t) {
|
|
29
|
+
return typeof t === 'string' ? t : t.default;
|
|
30
|
+
}
|
|
31
|
+
function resolveOgImages(input) {
|
|
32
|
+
if (input.openGraph?.images !== undefined)
|
|
33
|
+
return input.openGraph.images;
|
|
34
|
+
if (input.image !== undefined)
|
|
35
|
+
return [input.image];
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
function resolveTwitterImages(input, ogImages) {
|
|
39
|
+
if (input.twitter?.images !== undefined)
|
|
40
|
+
return input.twitter.images;
|
|
41
|
+
return ogImages;
|
|
42
|
+
}
|
|
43
|
+
function buildOpenGraph(input, ogImageUrls) {
|
|
44
|
+
const og = {
|
|
45
|
+
title: input.openGraph?.title ?? titleText(input.title),
|
|
46
|
+
description: input.openGraph?.description ?? input.description,
|
|
47
|
+
url: parseAbsoluteUrl(input.url),
|
|
48
|
+
type: input.openGraph?.type ?? 'website',
|
|
49
|
+
};
|
|
50
|
+
const siteName = input.openGraph?.siteName ?? input.siteName;
|
|
51
|
+
if (siteName !== undefined)
|
|
52
|
+
og.siteName = siteName;
|
|
53
|
+
const locale = input.openGraph?.locale ?? input.locale;
|
|
54
|
+
if (locale !== undefined)
|
|
55
|
+
og.locale = locale;
|
|
56
|
+
if (ogImageUrls.length > 0) {
|
|
57
|
+
og.images = ogImageUrls.map((url) => ({ url: parseAbsoluteUrl(url) }));
|
|
58
|
+
}
|
|
59
|
+
return og;
|
|
60
|
+
}
|
|
61
|
+
function buildTwitter(input, twitterImageUrls) {
|
|
62
|
+
const tw = {
|
|
63
|
+
card: input.twitter?.card ?? 'summary_large_image',
|
|
64
|
+
title: input.twitter?.title ?? input.openGraph?.title ?? titleText(input.title),
|
|
65
|
+
description: input.twitter?.description ?? input.openGraph?.description ?? input.description,
|
|
66
|
+
};
|
|
67
|
+
if (twitterImageUrls.length > 0) {
|
|
68
|
+
tw.images = twitterImageUrls.map((url) => parseAbsoluteUrl(url));
|
|
69
|
+
}
|
|
70
|
+
if (input.twitter?.site !== undefined)
|
|
71
|
+
tw.site = input.twitter.site;
|
|
72
|
+
return tw;
|
|
73
|
+
}
|
|
74
|
+
function buildRobots(r) {
|
|
75
|
+
const out = {};
|
|
76
|
+
if (r.index !== undefined)
|
|
77
|
+
out.index = r.index;
|
|
78
|
+
if (r.follow !== undefined)
|
|
79
|
+
out.follow = r.follow;
|
|
80
|
+
if (r.googleBot !== undefined) {
|
|
81
|
+
// Pass through the structured-record form; Next.js accepts it.
|
|
82
|
+
out.googleBot = r.googleBot;
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Compose a complete Next.js `Metadata` object from a single typed
|
|
88
|
+
* input. Drives audit checks **A1** (title length), **A3** (meta
|
|
89
|
+
* description length), **A4** (canonical URL), and the **F-series**
|
|
90
|
+
* (OpenGraph + Twitter cards).
|
|
91
|
+
*
|
|
92
|
+
* @throws SchemaValidationError batching every issue across title,
|
|
93
|
+
* description, and robots directives.
|
|
94
|
+
* @throws InvalidUrlError for the first malformed URL encountered
|
|
95
|
+
* (`url`, `image`, any `openGraph.images` entry, any
|
|
96
|
+
* `twitter.images` entry).
|
|
97
|
+
*/
|
|
98
|
+
export function defineSeo(input) {
|
|
99
|
+
const issues = [];
|
|
100
|
+
validateTitle(input.title, issues);
|
|
101
|
+
if (input.description.trim() === '') {
|
|
102
|
+
issues.push('description is empty');
|
|
103
|
+
}
|
|
104
|
+
if (input.robots !== undefined) {
|
|
105
|
+
validateRobots(input.robots, issues);
|
|
106
|
+
}
|
|
107
|
+
if (issues.length > 0) {
|
|
108
|
+
throw new SchemaValidationError(issues);
|
|
109
|
+
}
|
|
110
|
+
const canonicalUrl = parseAbsoluteUrl(input.url);
|
|
111
|
+
const ogImageUrls = resolveOgImages(input);
|
|
112
|
+
const twitterImageUrls = resolveTwitterImages(input, ogImageUrls);
|
|
113
|
+
const out = {
|
|
114
|
+
title: input.title,
|
|
115
|
+
description: input.description,
|
|
116
|
+
metadataBase: new URL(new URL(canonicalUrl).origin),
|
|
117
|
+
alternates: {
|
|
118
|
+
canonical: canonicalUrl,
|
|
119
|
+
},
|
|
120
|
+
openGraph: buildOpenGraph(input, ogImageUrls),
|
|
121
|
+
twitter: buildTwitter(input, twitterImageUrls),
|
|
122
|
+
};
|
|
123
|
+
if (input.robots !== undefined) {
|
|
124
|
+
out.robots = buildRobots(input.robots);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=define-seo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-seo.js","sourceRoot":"","sources":["../src/define-seo.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @answerable-kit/metadata — Next.js App Router metadata helpers for the
|
|
3
|
+
* Answerable SEO toolkit. Compose `title`, `description`, canonical
|
|
4
|
+
* URL, OpenGraph, Twitter cards, and robots directives from a single
|
|
5
|
+
* typed input, with smart fallbacks across the social-card chain.
|
|
6
|
+
*/
|
|
7
|
+
export declare const VERSION = "0.0.0";
|
|
8
|
+
export { defineSeo, type SeoGoogleBotDirectives, type SeoInput, type SeoOpenGraph, type SeoRobots, type SeoTitle, type SeoTwitter, } from './define-seo.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,KAAK,sBAAsB,EAC3B,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,UAAU,GAChB,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @answerable-kit/metadata — Next.js App Router metadata helpers for the
|
|
3
|
+
* Answerable SEO toolkit. Compose `title`, `description`, canonical
|
|
4
|
+
* URL, OpenGraph, Twitter cards, and robots directives from a single
|
|
5
|
+
* typed input, with smart fallbacks across the social-card chain.
|
|
6
|
+
*/
|
|
7
|
+
export const VERSION = '0.0.0';
|
|
8
|
+
export { defineSeo, } from './define-seo.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EACL,SAAS,GAOV,MAAM,iBAAiB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@answerable-kit/metadata",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Next.js App Router metadata helpers for the Answerable SEO toolkit.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Anuj Ojha",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/Anuj7411/answerable.git",
|
|
10
|
+
"directory": "packages/metadata"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Anuj7411/answerable/tree/main/packages/metadata#readme",
|
|
13
|
+
"bugs": "https://github.com/Anuj7411/answerable/issues",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": ["dist", "README.md"],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.build.json",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"clean": "rimraf dist .tsbuildinfo"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@answerable-kit/core": "workspace:*"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"next": ">=14.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"next": "^15.0.0",
|
|
43
|
+
"react": "^19.0.0",
|
|
44
|
+
"rimraf": "^6.0.1",
|
|
45
|
+
"typescript": "^5.7.3",
|
|
46
|
+
"vitest": "^3.0.5"
|
|
47
|
+
}
|
|
48
|
+
}
|