@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 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
@@ -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 };
@@ -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
+ }