@coldbydefault/next-seo-lite 1.0.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 +198 -0
- package/dist/index.d.mts +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +53 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# next-seo-lite
|
|
2
|
+
|
|
3
|
+
> **Stop writing 50 lines of Meta tags. Do it in 5.**
|
|
4
|
+
|
|
5
|
+
A tiny, zero-runtime-dependency SEO helper for [Next.js](https://nextjs.org/) App Router.
|
|
6
|
+
It turns simple props into the full `Metadata` object Next.js expects — including `openGraph`, `twitter`, and automatic canonical URLs.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Title suffixing** — `"Home"` becomes `"Home | MyBrand"` automatically.
|
|
13
|
+
- **OpenGraph** — fills `og:title`, `og:description`, and `og:image` in one call.
|
|
14
|
+
- **Twitter Cards** — maps your image to `summary_large_image` by default.
|
|
15
|
+
- **Canonical URLs** — combine a `baseUrl` with a page `path` and you're done.
|
|
16
|
+
- **Default fallback image** — set once at the root, used everywhere you don't override it.
|
|
17
|
+
- **Zero runtime dependencies** — only `next` as a peer dep (for types).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
This package is published to **both npm and GitHub Packages** — use whichever fits your workflow.
|
|
24
|
+
|
|
25
|
+
### Option A — npm (recommended, zero friction)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @coldbydefault/next-seo-lite
|
|
29
|
+
# or
|
|
30
|
+
yarn add @coldbydefault/next-seo-lite
|
|
31
|
+
# or
|
|
32
|
+
pnpm add @coldbydefault/next-seo-lite
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
No auth, no `.npmrc` changes needed. ✅
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### Option B — GitHub Packages
|
|
40
|
+
|
|
41
|
+
Useful if you're already inside a GitHub org/Actions workflow.
|
|
42
|
+
|
|
43
|
+
**1. Add to your project's `.npmrc`:**
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
@coldbydefault:registry=https://npm.pkg.github.com
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**2. Authenticate once** with a GitHub [Personal Access Token](https://github.com/settings/tokens) that has `read:packages` scope:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm login --registry=https://npm.pkg.github.com --scope=@coldbydefault
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**3. Install** (same command as npm):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install @coldbydefault/next-seo-lite
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
### 1. One-liner per page (standalone)
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// app/about/page.tsx
|
|
69
|
+
import { defineSEO } from "@coldbydefault/next-seo-lite";
|
|
70
|
+
import type { Metadata } from "next";
|
|
71
|
+
|
|
72
|
+
export const metadata: Metadata = defineSEO({
|
|
73
|
+
title: "About Us",
|
|
74
|
+
description: "Learn more about our team.",
|
|
75
|
+
siteName: "MyBrand",
|
|
76
|
+
image: "https://mysite.com/about-og.png",
|
|
77
|
+
baseUrl: "https://mysite.com",
|
|
78
|
+
path: "/about",
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
That one call replaces this:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
export const metadata: Metadata = {
|
|
86
|
+
title: "About Us | MyBrand",
|
|
87
|
+
description: "Learn more about our team.",
|
|
88
|
+
openGraph: {
|
|
89
|
+
title: "About Us | MyBrand",
|
|
90
|
+
description: "Learn more about our team.",
|
|
91
|
+
images: [{ url: "https://mysite.com/about-og.png" }],
|
|
92
|
+
type: "website",
|
|
93
|
+
siteName: "MyBrand",
|
|
94
|
+
url: "https://mysite.com/about",
|
|
95
|
+
},
|
|
96
|
+
twitter: {
|
|
97
|
+
card: "summary_large_image",
|
|
98
|
+
title: "About Us | MyBrand",
|
|
99
|
+
description: "Learn more about our team.",
|
|
100
|
+
images: ["https://mysite.com/about-og.png"],
|
|
101
|
+
},
|
|
102
|
+
alternates: {
|
|
103
|
+
canonical: "https://mysite.com/about",
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### 2. Global defaults with `createSEOConfig` (recommended)
|
|
111
|
+
|
|
112
|
+
Define your globals once, then call the returned function on every page.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// lib/seo.ts
|
|
116
|
+
import { createSEOConfig } from "@coldbydefault/next-seo-lite";
|
|
117
|
+
|
|
118
|
+
export const defineSEO = createSEOConfig({
|
|
119
|
+
siteName: "MyBrand",
|
|
120
|
+
baseUrl: "https://mysite.com",
|
|
121
|
+
defaultImage: "https://mysite.com/og-default.png",
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
// app/layout.tsx
|
|
127
|
+
import { defineSEO } from "@/lib/seo";
|
|
128
|
+
import type { Metadata } from "next";
|
|
129
|
+
|
|
130
|
+
export const metadata: Metadata = defineSEO({
|
|
131
|
+
title: "Home",
|
|
132
|
+
description: "Welcome to MyBrand.",
|
|
133
|
+
path: "/",
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// app/blog/[slug]/page.tsx
|
|
139
|
+
import { defineSEO } from "@/lib/seo";
|
|
140
|
+
|
|
141
|
+
export async function generateMetadata({
|
|
142
|
+
params,
|
|
143
|
+
}: {
|
|
144
|
+
params: { slug: string };
|
|
145
|
+
}) {
|
|
146
|
+
const post = await getPost(params.slug);
|
|
147
|
+
|
|
148
|
+
return defineSEO({
|
|
149
|
+
title: post.title,
|
|
150
|
+
description: post.summary,
|
|
151
|
+
image: post.coverImage, // overrides the global defaultImage
|
|
152
|
+
path: `/blog/${params.slug}`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## API Reference
|
|
160
|
+
|
|
161
|
+
### `defineSEO(props: SEOProps): Metadata`
|
|
162
|
+
|
|
163
|
+
Standalone helper — no global config needed.
|
|
164
|
+
|
|
165
|
+
### `createSEOConfig(config: SEOConfig): (props: SEOProps) => Metadata`
|
|
166
|
+
|
|
167
|
+
Returns a `defineSEO` function pre-loaded with your global defaults.
|
|
168
|
+
Page-level props always win over the global config.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### `SEOConfig`
|
|
173
|
+
|
|
174
|
+
| Prop | Type | Description |
|
|
175
|
+
| -------------- | -------- | -------------------------------------------------------------------- | ----------- |
|
|
176
|
+
| `siteName` | `string` | Appended to every title: `"Page | SiteName"`. |
|
|
177
|
+
| `baseUrl` | `string` | Absolute base URL for canonical links, e.g. `"https://example.com"`. |
|
|
178
|
+
| `defaultImage` | `string` | Fallback OG / Twitter image URL. |
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### `SEOProps`
|
|
183
|
+
|
|
184
|
+
| Prop | Type | Required | Description |
|
|
185
|
+
| -------------- | -------- | -------- | ------------------------------------------------------------------ |
|
|
186
|
+
| `title` | `string` | ✅ | Page title (without suffix). |
|
|
187
|
+
| `description` | `string` | ✅ | Short page description. |
|
|
188
|
+
| `image` | `string` | — | Overrides `defaultImage`. |
|
|
189
|
+
| `path` | `string` | — | Slug appended to `baseUrl` for the canonical URL, e.g. `"/about"`. |
|
|
190
|
+
| `siteName` | `string` | — | Overrides global `siteName`. Pass `""` to suppress the suffix. |
|
|
191
|
+
| `baseUrl` | `string` | — | Overrides global `baseUrl`. |
|
|
192
|
+
| `defaultImage` | `string` | — | Overrides global `defaultImage` for this call. |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Metadata } from 'next';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global defaults applied to every page when you don't override them.
|
|
5
|
+
* Pass this once at the root layout level via `createSEOConfig`.
|
|
6
|
+
*/
|
|
7
|
+
interface SEOConfig {
|
|
8
|
+
/** Your brand / product name, appended to every page title. */
|
|
9
|
+
siteName?: string;
|
|
10
|
+
/** Absolute base URL used for canonical links, e.g. "https://example.com". */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Fallback OG/Twitter image URL used when a page omits its own image. */
|
|
13
|
+
defaultImage?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Per-page SEO props passed to `defineSEO`.
|
|
17
|
+
*/
|
|
18
|
+
interface SEOProps {
|
|
19
|
+
/** Page-level title (without the site name suffix). */
|
|
20
|
+
title: string;
|
|
21
|
+
/** Short description shown in search results and social previews. */
|
|
22
|
+
description: string;
|
|
23
|
+
/**
|
|
24
|
+
* Absolute or relative path for this page's OG/Twitter image.
|
|
25
|
+
* Falls back to `SEOConfig.defaultImage` when omitted.
|
|
26
|
+
*/
|
|
27
|
+
image?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Slug / path appended to `SEOConfig.baseUrl` to build the canonical URL,
|
|
30
|
+
* e.g. "/about". Ignored when no `baseUrl` is configured.
|
|
31
|
+
*/
|
|
32
|
+
path?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Override the global `siteName` for this specific page.
|
|
35
|
+
* Set to `""` (empty string) to suppress the suffix entirely.
|
|
36
|
+
*/
|
|
37
|
+
siteName?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Override the global `baseUrl` for this specific page.
|
|
40
|
+
*/
|
|
41
|
+
baseUrl?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Override the global `defaultImage` for this page.
|
|
44
|
+
*/
|
|
45
|
+
defaultImage?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a `defineSEO` function pre-loaded with your global defaults.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // lib/seo.ts
|
|
52
|
+
* import { createSEOConfig } from 'next-seo-lite';
|
|
53
|
+
*
|
|
54
|
+
* export const defineSEO = createSEOConfig({
|
|
55
|
+
* siteName: 'MyBrand',
|
|
56
|
+
* baseUrl: 'https://mybrand.com',
|
|
57
|
+
* defaultImage: 'https://mybrand.com/og-default.png',
|
|
58
|
+
* });
|
|
59
|
+
*/
|
|
60
|
+
declare function createSEOConfig(config?: SEOConfig): (props: SEOProps) => Metadata;
|
|
61
|
+
/**
|
|
62
|
+
* One-shot helper when you don't need global defaults.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // app/page.tsx
|
|
66
|
+
* import { defineSEO } from 'next-seo-lite';
|
|
67
|
+
*
|
|
68
|
+
* export const metadata = defineSEO({
|
|
69
|
+
* title: 'Home',
|
|
70
|
+
* description: 'Welcome to my site.',
|
|
71
|
+
* siteName: 'MyBrand',
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
74
|
+
declare function defineSEO(props: SEOProps): Metadata;
|
|
75
|
+
|
|
76
|
+
export { type SEOConfig, type SEOProps, createSEOConfig, defineSEO };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Metadata } from 'next';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global defaults applied to every page when you don't override them.
|
|
5
|
+
* Pass this once at the root layout level via `createSEOConfig`.
|
|
6
|
+
*/
|
|
7
|
+
interface SEOConfig {
|
|
8
|
+
/** Your brand / product name, appended to every page title. */
|
|
9
|
+
siteName?: string;
|
|
10
|
+
/** Absolute base URL used for canonical links, e.g. "https://example.com". */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Fallback OG/Twitter image URL used when a page omits its own image. */
|
|
13
|
+
defaultImage?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Per-page SEO props passed to `defineSEO`.
|
|
17
|
+
*/
|
|
18
|
+
interface SEOProps {
|
|
19
|
+
/** Page-level title (without the site name suffix). */
|
|
20
|
+
title: string;
|
|
21
|
+
/** Short description shown in search results and social previews. */
|
|
22
|
+
description: string;
|
|
23
|
+
/**
|
|
24
|
+
* Absolute or relative path for this page's OG/Twitter image.
|
|
25
|
+
* Falls back to `SEOConfig.defaultImage` when omitted.
|
|
26
|
+
*/
|
|
27
|
+
image?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Slug / path appended to `SEOConfig.baseUrl` to build the canonical URL,
|
|
30
|
+
* e.g. "/about". Ignored when no `baseUrl` is configured.
|
|
31
|
+
*/
|
|
32
|
+
path?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Override the global `siteName` for this specific page.
|
|
35
|
+
* Set to `""` (empty string) to suppress the suffix entirely.
|
|
36
|
+
*/
|
|
37
|
+
siteName?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Override the global `baseUrl` for this specific page.
|
|
40
|
+
*/
|
|
41
|
+
baseUrl?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Override the global `defaultImage` for this page.
|
|
44
|
+
*/
|
|
45
|
+
defaultImage?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a `defineSEO` function pre-loaded with your global defaults.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // lib/seo.ts
|
|
52
|
+
* import { createSEOConfig } from 'next-seo-lite';
|
|
53
|
+
*
|
|
54
|
+
* export const defineSEO = createSEOConfig({
|
|
55
|
+
* siteName: 'MyBrand',
|
|
56
|
+
* baseUrl: 'https://mybrand.com',
|
|
57
|
+
* defaultImage: 'https://mybrand.com/og-default.png',
|
|
58
|
+
* });
|
|
59
|
+
*/
|
|
60
|
+
declare function createSEOConfig(config?: SEOConfig): (props: SEOProps) => Metadata;
|
|
61
|
+
/**
|
|
62
|
+
* One-shot helper when you don't need global defaults.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // app/page.tsx
|
|
66
|
+
* import { defineSEO } from 'next-seo-lite';
|
|
67
|
+
*
|
|
68
|
+
* export const metadata = defineSEO({
|
|
69
|
+
* title: 'Home',
|
|
70
|
+
* description: 'Welcome to my site.',
|
|
71
|
+
* siteName: 'MyBrand',
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
74
|
+
declare function defineSEO(props: SEOProps): Metadata;
|
|
75
|
+
|
|
76
|
+
export { type SEOConfig, type SEOProps, createSEOConfig, defineSEO };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createSEOConfig: () => createSEOConfig,
|
|
24
|
+
defineSEO: () => defineSEO
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
function createSEOConfig(config = {}) {
|
|
28
|
+
return function defineSEO2(props) {
|
|
29
|
+
return buildMetadata(props, config);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function defineSEO(props) {
|
|
33
|
+
return buildMetadata(props, {});
|
|
34
|
+
}
|
|
35
|
+
function buildMetadata(props, config) {
|
|
36
|
+
const siteName = props.siteName !== void 0 ? props.siteName : config.siteName;
|
|
37
|
+
const baseUrl = props.baseUrl ?? config.baseUrl;
|
|
38
|
+
const image = props.image ?? props.defaultImage ?? config.defaultImage;
|
|
39
|
+
const fullTitle = siteName !== void 0 && siteName !== "" ? `${props.title} | ${siteName}` : props.title;
|
|
40
|
+
let canonical;
|
|
41
|
+
if (baseUrl) {
|
|
42
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
43
|
+
const normalized = props.path ? props.path.replace(/^\//, "") : "";
|
|
44
|
+
const slug = normalized ? `/${normalized}` : "";
|
|
45
|
+
canonical = `${base}${slug}`;
|
|
46
|
+
}
|
|
47
|
+
const ogImages = image ? [{ url: image }] : [];
|
|
48
|
+
const twitterImages = image ? [image] : [];
|
|
49
|
+
const metadata = {
|
|
50
|
+
title: fullTitle,
|
|
51
|
+
description: props.description,
|
|
52
|
+
openGraph: {
|
|
53
|
+
title: fullTitle,
|
|
54
|
+
description: props.description,
|
|
55
|
+
...ogImages.length > 0 && { images: ogImages },
|
|
56
|
+
type: "website",
|
|
57
|
+
...siteName && { siteName },
|
|
58
|
+
...canonical && { url: canonical }
|
|
59
|
+
},
|
|
60
|
+
twitter: {
|
|
61
|
+
card: "summary_large_image",
|
|
62
|
+
title: fullTitle,
|
|
63
|
+
description: props.description,
|
|
64
|
+
...twitterImages.length > 0 && { images: twitterImages }
|
|
65
|
+
},
|
|
66
|
+
...canonical && {
|
|
67
|
+
alternates: {
|
|
68
|
+
canonical
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return metadata;
|
|
73
|
+
}
|
|
74
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
75
|
+
0 && (module.exports = {
|
|
76
|
+
createSEOConfig,
|
|
77
|
+
defineSEO
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Metadata } from \"next\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Global defaults applied to every page when you don't override them.\r\n * Pass this once at the root layout level via `createSEOConfig`.\r\n */\r\nexport interface SEOConfig {\r\n /** Your brand / product name, appended to every page title. */\r\n siteName?: string;\r\n /** Absolute base URL used for canonical links, e.g. \"https://example.com\". */\r\n baseUrl?: string;\r\n /** Fallback OG/Twitter image URL used when a page omits its own image. */\r\n defaultImage?: string;\r\n}\r\n\r\n/**\r\n * Per-page SEO props passed to `defineSEO`.\r\n */\r\nexport interface SEOProps {\r\n /** Page-level title (without the site name suffix). */\r\n title: string;\r\n /** Short description shown in search results and social previews. */\r\n description: string;\r\n /**\r\n * Absolute or relative path for this page's OG/Twitter image.\r\n * Falls back to `SEOConfig.defaultImage` when omitted.\r\n */\r\n image?: string;\r\n /**\r\n * Slug / path appended to `SEOConfig.baseUrl` to build the canonical URL,\r\n * e.g. \"/about\". Ignored when no `baseUrl` is configured.\r\n */\r\n path?: string;\r\n /**\r\n * Override the global `siteName` for this specific page.\r\n * Set to `\"\"` (empty string) to suppress the suffix entirely.\r\n */\r\n siteName?: string;\r\n /**\r\n * Override the global `baseUrl` for this specific page.\r\n */\r\n baseUrl?: string;\r\n /**\r\n * Override the global `defaultImage` for this page.\r\n */\r\n defaultImage?: string;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Factory\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Creates a `defineSEO` function pre-loaded with your global defaults.\r\n *\r\n * @example\r\n * // lib/seo.ts\r\n * import { createSEOConfig } from 'next-seo-lite';\r\n *\r\n * export const defineSEO = createSEOConfig({\r\n * siteName: 'MyBrand',\r\n * baseUrl: 'https://mybrand.com',\r\n * defaultImage: 'https://mybrand.com/og-default.png',\r\n * });\r\n */\r\nexport function createSEOConfig(config: SEOConfig = {}) {\r\n return function defineSEO(props: SEOProps): Metadata {\r\n return buildMetadata(props, config);\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Standalone helper (zero-config)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * One-shot helper when you don't need global defaults.\r\n *\r\n * @example\r\n * // app/page.tsx\r\n * import { defineSEO } from 'next-seo-lite';\r\n *\r\n * export const metadata = defineSEO({\r\n * title: 'Home',\r\n * description: 'Welcome to my site.',\r\n * siteName: 'MyBrand',\r\n * });\r\n */\r\nexport function defineSEO(props: SEOProps): Metadata {\r\n return buildMetadata(props, {});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Core builder\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction buildMetadata(props: SEOProps, config: SEOConfig): Metadata {\r\n // ---- Resolve values (page-level wins over global config) ----------------\r\n const siteName =\r\n props.siteName !== undefined ? props.siteName : config.siteName;\r\n const baseUrl = props.baseUrl ?? config.baseUrl;\r\n const image = props.image ?? props.defaultImage ?? config.defaultImage;\r\n\r\n // ---- Title --------------------------------------------------------------\r\n const fullTitle =\r\n siteName !== undefined && siteName !== \"\"\r\n ? `${props.title} | ${siteName}`\r\n : props.title;\r\n\r\n // ---- Canonical URL ------------------------------------------------------\r\n let canonical: string | undefined;\r\n if (baseUrl) {\r\n const base = baseUrl.replace(/\\/$/, \"\");\r\n const normalized = props.path ? props.path.replace(/^\\//, \"\") : \"\";\r\n const slug = normalized ? `/${normalized}` : \"\";\r\n canonical = `${base}${slug}`;\r\n }\r\n\r\n // ---- OG images ----------------------------------------------------------\r\n const ogImages: { url: string }[] = image ? [{ url: image }] : [];\r\n\r\n // ---- Twitter images -----------------------------------------------------\r\n const twitterImages: string[] = image ? [image] : [];\r\n\r\n // ---- Assemble Metadata --------------------------------------------------\r\n const metadata: Metadata = {\r\n title: fullTitle,\r\n description: props.description,\r\n openGraph: {\r\n title: fullTitle,\r\n description: props.description,\r\n ...(ogImages.length > 0 && { images: ogImages }),\r\n type: \"website\",\r\n ...(siteName && { siteName }),\r\n ...(canonical && { url: canonical }),\r\n },\r\n twitter: {\r\n card: \"summary_large_image\",\r\n title: fullTitle,\r\n description: props.description,\r\n ...(twitterImages.length > 0 && { images: twitterImages }),\r\n },\r\n ...(canonical && {\r\n alternates: {\r\n canonical,\r\n },\r\n }),\r\n };\r\n\r\n return metadata;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqEO,SAAS,gBAAgB,SAAoB,CAAC,GAAG;AACtD,SAAO,SAASA,WAAU,OAA2B;AACnD,WAAO,cAAc,OAAO,MAAM;AAAA,EACpC;AACF;AAmBO,SAAS,UAAU,OAA2B;AACnD,SAAO,cAAc,OAAO,CAAC,CAAC;AAChC;AAMA,SAAS,cAAc,OAAiB,QAA6B;AAEnE,QAAM,WACJ,MAAM,aAAa,SAAY,MAAM,WAAW,OAAO;AACzD,QAAM,UAAU,MAAM,WAAW,OAAO;AACxC,QAAM,QAAQ,MAAM,SAAS,MAAM,gBAAgB,OAAO;AAG1D,QAAM,YACJ,aAAa,UAAa,aAAa,KACnC,GAAG,MAAM,KAAK,MAAM,QAAQ,KAC5B,MAAM;AAGZ,MAAI;AACJ,MAAI,SAAS;AACX,UAAM,OAAO,QAAQ,QAAQ,OAAO,EAAE;AACtC,UAAM,aAAa,MAAM,OAAO,MAAM,KAAK,QAAQ,OAAO,EAAE,IAAI;AAChE,UAAM,OAAO,aAAa,IAAI,UAAU,KAAK;AAC7C,gBAAY,GAAG,IAAI,GAAG,IAAI;AAAA,EAC5B;AAGA,QAAM,WAA8B,QAAQ,CAAC,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC;AAGhE,QAAM,gBAA0B,QAAQ,CAAC,KAAK,IAAI,CAAC;AAGnD,QAAM,WAAqB;AAAA,IACzB,OAAO;AAAA,IACP,aAAa,MAAM;AAAA,IACnB,WAAW;AAAA,MACT,OAAO;AAAA,MACP,aAAa,MAAM;AAAA,MACnB,GAAI,SAAS,SAAS,KAAK,EAAE,QAAQ,SAAS;AAAA,MAC9C,MAAM;AAAA,MACN,GAAI,YAAY,EAAE,SAAS;AAAA,MAC3B,GAAI,aAAa,EAAE,KAAK,UAAU;AAAA,IACpC;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa,MAAM;AAAA,MACnB,GAAI,cAAc,SAAS,KAAK,EAAE,QAAQ,cAAc;AAAA,IAC1D;AAAA,IACA,GAAI,aAAa;AAAA,MACf,YAAY;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":["defineSEO"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
function createSEOConfig(config = {}) {
|
|
3
|
+
return function defineSEO2(props) {
|
|
4
|
+
return buildMetadata(props, config);
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
function defineSEO(props) {
|
|
8
|
+
return buildMetadata(props, {});
|
|
9
|
+
}
|
|
10
|
+
function buildMetadata(props, config) {
|
|
11
|
+
const siteName = props.siteName !== void 0 ? props.siteName : config.siteName;
|
|
12
|
+
const baseUrl = props.baseUrl ?? config.baseUrl;
|
|
13
|
+
const image = props.image ?? props.defaultImage ?? config.defaultImage;
|
|
14
|
+
const fullTitle = siteName !== void 0 && siteName !== "" ? `${props.title} | ${siteName}` : props.title;
|
|
15
|
+
let canonical;
|
|
16
|
+
if (baseUrl) {
|
|
17
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
18
|
+
const normalized = props.path ? props.path.replace(/^\//, "") : "";
|
|
19
|
+
const slug = normalized ? `/${normalized}` : "";
|
|
20
|
+
canonical = `${base}${slug}`;
|
|
21
|
+
}
|
|
22
|
+
const ogImages = image ? [{ url: image }] : [];
|
|
23
|
+
const twitterImages = image ? [image] : [];
|
|
24
|
+
const metadata = {
|
|
25
|
+
title: fullTitle,
|
|
26
|
+
description: props.description,
|
|
27
|
+
openGraph: {
|
|
28
|
+
title: fullTitle,
|
|
29
|
+
description: props.description,
|
|
30
|
+
...ogImages.length > 0 && { images: ogImages },
|
|
31
|
+
type: "website",
|
|
32
|
+
...siteName && { siteName },
|
|
33
|
+
...canonical && { url: canonical }
|
|
34
|
+
},
|
|
35
|
+
twitter: {
|
|
36
|
+
card: "summary_large_image",
|
|
37
|
+
title: fullTitle,
|
|
38
|
+
description: props.description,
|
|
39
|
+
...twitterImages.length > 0 && { images: twitterImages }
|
|
40
|
+
},
|
|
41
|
+
...canonical && {
|
|
42
|
+
alternates: {
|
|
43
|
+
canonical
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return metadata;
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
createSEOConfig,
|
|
51
|
+
defineSEO
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Metadata } from \"next\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Global defaults applied to every page when you don't override them.\r\n * Pass this once at the root layout level via `createSEOConfig`.\r\n */\r\nexport interface SEOConfig {\r\n /** Your brand / product name, appended to every page title. */\r\n siteName?: string;\r\n /** Absolute base URL used for canonical links, e.g. \"https://example.com\". */\r\n baseUrl?: string;\r\n /** Fallback OG/Twitter image URL used when a page omits its own image. */\r\n defaultImage?: string;\r\n}\r\n\r\n/**\r\n * Per-page SEO props passed to `defineSEO`.\r\n */\r\nexport interface SEOProps {\r\n /** Page-level title (without the site name suffix). */\r\n title: string;\r\n /** Short description shown in search results and social previews. */\r\n description: string;\r\n /**\r\n * Absolute or relative path for this page's OG/Twitter image.\r\n * Falls back to `SEOConfig.defaultImage` when omitted.\r\n */\r\n image?: string;\r\n /**\r\n * Slug / path appended to `SEOConfig.baseUrl` to build the canonical URL,\r\n * e.g. \"/about\". Ignored when no `baseUrl` is configured.\r\n */\r\n path?: string;\r\n /**\r\n * Override the global `siteName` for this specific page.\r\n * Set to `\"\"` (empty string) to suppress the suffix entirely.\r\n */\r\n siteName?: string;\r\n /**\r\n * Override the global `baseUrl` for this specific page.\r\n */\r\n baseUrl?: string;\r\n /**\r\n * Override the global `defaultImage` for this page.\r\n */\r\n defaultImage?: string;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Factory\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Creates a `defineSEO` function pre-loaded with your global defaults.\r\n *\r\n * @example\r\n * // lib/seo.ts\r\n * import { createSEOConfig } from 'next-seo-lite';\r\n *\r\n * export const defineSEO = createSEOConfig({\r\n * siteName: 'MyBrand',\r\n * baseUrl: 'https://mybrand.com',\r\n * defaultImage: 'https://mybrand.com/og-default.png',\r\n * });\r\n */\r\nexport function createSEOConfig(config: SEOConfig = {}) {\r\n return function defineSEO(props: SEOProps): Metadata {\r\n return buildMetadata(props, config);\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Standalone helper (zero-config)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * One-shot helper when you don't need global defaults.\r\n *\r\n * @example\r\n * // app/page.tsx\r\n * import { defineSEO } from 'next-seo-lite';\r\n *\r\n * export const metadata = defineSEO({\r\n * title: 'Home',\r\n * description: 'Welcome to my site.',\r\n * siteName: 'MyBrand',\r\n * });\r\n */\r\nexport function defineSEO(props: SEOProps): Metadata {\r\n return buildMetadata(props, {});\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Core builder\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction buildMetadata(props: SEOProps, config: SEOConfig): Metadata {\r\n // ---- Resolve values (page-level wins over global config) ----------------\r\n const siteName =\r\n props.siteName !== undefined ? props.siteName : config.siteName;\r\n const baseUrl = props.baseUrl ?? config.baseUrl;\r\n const image = props.image ?? props.defaultImage ?? config.defaultImage;\r\n\r\n // ---- Title --------------------------------------------------------------\r\n const fullTitle =\r\n siteName !== undefined && siteName !== \"\"\r\n ? `${props.title} | ${siteName}`\r\n : props.title;\r\n\r\n // ---- Canonical URL ------------------------------------------------------\r\n let canonical: string | undefined;\r\n if (baseUrl) {\r\n const base = baseUrl.replace(/\\/$/, \"\");\r\n const normalized = props.path ? props.path.replace(/^\\//, \"\") : \"\";\r\n const slug = normalized ? `/${normalized}` : \"\";\r\n canonical = `${base}${slug}`;\r\n }\r\n\r\n // ---- OG images ----------------------------------------------------------\r\n const ogImages: { url: string }[] = image ? [{ url: image }] : [];\r\n\r\n // ---- Twitter images -----------------------------------------------------\r\n const twitterImages: string[] = image ? [image] : [];\r\n\r\n // ---- Assemble Metadata --------------------------------------------------\r\n const metadata: Metadata = {\r\n title: fullTitle,\r\n description: props.description,\r\n openGraph: {\r\n title: fullTitle,\r\n description: props.description,\r\n ...(ogImages.length > 0 && { images: ogImages }),\r\n type: \"website\",\r\n ...(siteName && { siteName }),\r\n ...(canonical && { url: canonical }),\r\n },\r\n twitter: {\r\n card: \"summary_large_image\",\r\n title: fullTitle,\r\n description: props.description,\r\n ...(twitterImages.length > 0 && { images: twitterImages }),\r\n },\r\n ...(canonical && {\r\n alternates: {\r\n canonical,\r\n },\r\n }),\r\n };\r\n\r\n return metadata;\r\n}\r\n"],"mappings":";AAqEO,SAAS,gBAAgB,SAAoB,CAAC,GAAG;AACtD,SAAO,SAASA,WAAU,OAA2B;AACnD,WAAO,cAAc,OAAO,MAAM;AAAA,EACpC;AACF;AAmBO,SAAS,UAAU,OAA2B;AACnD,SAAO,cAAc,OAAO,CAAC,CAAC;AAChC;AAMA,SAAS,cAAc,OAAiB,QAA6B;AAEnE,QAAM,WACJ,MAAM,aAAa,SAAY,MAAM,WAAW,OAAO;AACzD,QAAM,UAAU,MAAM,WAAW,OAAO;AACxC,QAAM,QAAQ,MAAM,SAAS,MAAM,gBAAgB,OAAO;AAG1D,QAAM,YACJ,aAAa,UAAa,aAAa,KACnC,GAAG,MAAM,KAAK,MAAM,QAAQ,KAC5B,MAAM;AAGZ,MAAI;AACJ,MAAI,SAAS;AACX,UAAM,OAAO,QAAQ,QAAQ,OAAO,EAAE;AACtC,UAAM,aAAa,MAAM,OAAO,MAAM,KAAK,QAAQ,OAAO,EAAE,IAAI;AAChE,UAAM,OAAO,aAAa,IAAI,UAAU,KAAK;AAC7C,gBAAY,GAAG,IAAI,GAAG,IAAI;AAAA,EAC5B;AAGA,QAAM,WAA8B,QAAQ,CAAC,EAAE,KAAK,MAAM,CAAC,IAAI,CAAC;AAGhE,QAAM,gBAA0B,QAAQ,CAAC,KAAK,IAAI,CAAC;AAGnD,QAAM,WAAqB;AAAA,IACzB,OAAO;AAAA,IACP,aAAa,MAAM;AAAA,IACnB,WAAW;AAAA,MACT,OAAO;AAAA,MACP,aAAa,MAAM;AAAA,MACnB,GAAI,SAAS,SAAS,KAAK,EAAE,QAAQ,SAAS;AAAA,MAC9C,MAAM;AAAA,MACN,GAAI,YAAY,EAAE,SAAS;AAAA,MAC3B,GAAI,aAAa,EAAE,KAAK,UAAU;AAAA,IACpC;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa,MAAM;AAAA,MACnB,GAAI,cAAc,SAAS,KAAK,EAAE,QAAQ,cAAc;AAAA,IAC1D;AAAA,IACA,GAAI,aAAa;AAAA,MACf,YAAY;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":["defineSEO"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coldbydefault/next-seo-lite",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Stop writing 50 lines of Meta tags. Do it in 5. A zero-dependency SEO helper for Next.js.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/coldbydefault/next-seo-lite.git"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"module": "dist/index.mjs",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"require": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"prepublishOnly": "npm run build",
|
|
28
|
+
"publish:npm": "npm publish --access public --registry https://registry.npmjs.org",
|
|
29
|
+
"publish:github": "npm publish --access public --registry https://npm.pkg.github.com",
|
|
30
|
+
"release": "npm run test && npm run publish:npm && npm run publish:github"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"next.js",
|
|
34
|
+
"seo",
|
|
35
|
+
"metadata",
|
|
36
|
+
"opengraph",
|
|
37
|
+
"twitter-card",
|
|
38
|
+
"canonical"
|
|
39
|
+
],
|
|
40
|
+
"author": "",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"next": ">=13.0.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"next": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"next": "^15.0.0",
|
|
52
|
+
"tsup": "^8.0.0",
|
|
53
|
+
"typescript": "^5.0.0",
|
|
54
|
+
"vitest": "^2.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|