@amjadiqbal/start-seo-kit 0.0.0-stage → 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/CHANGELOG.md +22 -0
- package/LICENSE.md +21 -0
- package/README.md +318 -2
- package/dist/commerce.d.ts +151 -0
- package/dist/commerce.js +315 -0
- package/dist/head-D2pPoGNP.d.ts +218 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +420 -0
- package/dist/json-ld-BkYFVGHw.js +122 -0
- package/dist/og.d.ts +64 -0
- package/dist/og.js +142 -0
- package/dist/sitemap-DbEixSCi.d.ts +98 -0
- package/package.json +89 -4
package/dist/commerce.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { t as jsonLd } from "./json-ld-BkYFVGHw.js";
|
|
2
|
+
// src/commerce.ts
|
|
3
|
+
const CONTEXT = "https://schema.org";
|
|
4
|
+
const SCHEMA = "https://schema.org/";
|
|
5
|
+
/** Product JSON-LD (with Offer or AggregateOffer, ratings, reviews, shipping and returns) for Google product rich results. */
|
|
6
|
+
function productJsonLd(p) {
|
|
7
|
+
return clean({
|
|
8
|
+
"@context": CONTEXT,
|
|
9
|
+
"@type": "Product",
|
|
10
|
+
"@id": p.id,
|
|
11
|
+
name: p.name,
|
|
12
|
+
description: p.description,
|
|
13
|
+
image: p.image,
|
|
14
|
+
url: p.url,
|
|
15
|
+
sku: p.sku,
|
|
16
|
+
mpn: p.mpn,
|
|
17
|
+
gtin: p.gtin,
|
|
18
|
+
brand: p.brand ? {
|
|
19
|
+
"@type": "Brand",
|
|
20
|
+
name: p.brand
|
|
21
|
+
} : void 0,
|
|
22
|
+
category: p.category,
|
|
23
|
+
color: p.color,
|
|
24
|
+
size: p.size,
|
|
25
|
+
material: p.material,
|
|
26
|
+
inProductGroupWithID: p.groupId,
|
|
27
|
+
offers: p.offers === void 0 ? void 0 : offersLd(p.offers),
|
|
28
|
+
aggregateRating: p.rating ? aggregateRatingLd(p.rating) : void 0,
|
|
29
|
+
review: p.reviews?.length ? p.reviews.map(reviewLd) : void 0
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function offersLd(o) {
|
|
33
|
+
if (Array.isArray(o)) return o.map(offerLd);
|
|
34
|
+
return "lowPrice" in o ? aggregateOfferLd(o) : offerLd(o);
|
|
35
|
+
}
|
|
36
|
+
/** One `Offer`. */
|
|
37
|
+
function offerLd(o) {
|
|
38
|
+
const shipping = o.shipping === void 0 ? void 0 : Array.isArray(o.shipping) ? o.shipping : [o.shipping];
|
|
39
|
+
return clean({
|
|
40
|
+
"@type": "Offer",
|
|
41
|
+
price: o.price,
|
|
42
|
+
priceCurrency: o.currency,
|
|
43
|
+
availability: o.availability ? SCHEMA + o.availability : void 0,
|
|
44
|
+
itemCondition: o.condition ? SCHEMA + o.condition : void 0,
|
|
45
|
+
url: o.url,
|
|
46
|
+
priceValidUntil: dateOnly(o.priceValidUntil),
|
|
47
|
+
sku: o.sku,
|
|
48
|
+
seller: o.seller ? {
|
|
49
|
+
"@type": "Organization",
|
|
50
|
+
name: o.seller
|
|
51
|
+
} : void 0,
|
|
52
|
+
shippingDetails: shipping?.length ? shipping.map(shippingLd) : void 0,
|
|
53
|
+
hasMerchantReturnPolicy: o.returnPolicy ? returnPolicyLd(o.returnPolicy) : void 0
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** An `AggregateOffer` (price range across sellers or variants). */
|
|
57
|
+
function aggregateOfferLd(o) {
|
|
58
|
+
return clean({
|
|
59
|
+
"@type": "AggregateOffer",
|
|
60
|
+
lowPrice: o.lowPrice,
|
|
61
|
+
highPrice: o.highPrice,
|
|
62
|
+
priceCurrency: o.currency,
|
|
63
|
+
offerCount: o.offerCount,
|
|
64
|
+
availability: o.availability ? SCHEMA + o.availability : void 0
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function aggregateRatingLd(r) {
|
|
68
|
+
return clean({
|
|
69
|
+
"@type": "AggregateRating",
|
|
70
|
+
ratingValue: r.value,
|
|
71
|
+
ratingCount: r.count,
|
|
72
|
+
reviewCount: r.reviewCount,
|
|
73
|
+
bestRating: r.best,
|
|
74
|
+
worstRating: r.worst
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function reviewLd(r) {
|
|
78
|
+
const notes = (items) => items?.length ? {
|
|
79
|
+
"@type": "ItemList",
|
|
80
|
+
itemListElement: items.map((name, i) => ({
|
|
81
|
+
"@type": "ListItem",
|
|
82
|
+
position: i + 1,
|
|
83
|
+
name
|
|
84
|
+
}))
|
|
85
|
+
} : void 0;
|
|
86
|
+
return clean({
|
|
87
|
+
"@type": "Review",
|
|
88
|
+
author: {
|
|
89
|
+
"@type": "Person",
|
|
90
|
+
name: r.author
|
|
91
|
+
},
|
|
92
|
+
reviewRating: clean({
|
|
93
|
+
"@type": "Rating",
|
|
94
|
+
ratingValue: r.rating,
|
|
95
|
+
bestRating: r.best ?? 5,
|
|
96
|
+
worstRating: r.worst
|
|
97
|
+
}),
|
|
98
|
+
name: r.name,
|
|
99
|
+
reviewBody: r.body,
|
|
100
|
+
datePublished: dateOnly(r.datePublished),
|
|
101
|
+
positiveNotes: notes(r.positiveNotes),
|
|
102
|
+
negativeNotes: notes(r.negativeNotes)
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** `OfferShippingDetails`. */
|
|
106
|
+
function shippingLd(s) {
|
|
107
|
+
const countries = Array.isArray(s.country) ? s.country : [s.country];
|
|
108
|
+
const region = (country) => clean({
|
|
109
|
+
"@type": "DefinedRegion",
|
|
110
|
+
addressCountry: country,
|
|
111
|
+
addressRegion: s.regions
|
|
112
|
+
});
|
|
113
|
+
const range = (days) => days ? {
|
|
114
|
+
"@type": "QuantitativeValue",
|
|
115
|
+
minValue: days[0],
|
|
116
|
+
maxValue: days[1],
|
|
117
|
+
unitCode: "DAY"
|
|
118
|
+
} : void 0;
|
|
119
|
+
return clean({
|
|
120
|
+
"@type": "OfferShippingDetails",
|
|
121
|
+
shippingRate: {
|
|
122
|
+
"@type": "MonetaryAmount",
|
|
123
|
+
value: s.rate,
|
|
124
|
+
currency: s.currency
|
|
125
|
+
},
|
|
126
|
+
shippingDestination: countries.length === 1 ? region(countries[0]) : countries.map(region),
|
|
127
|
+
deliveryTime: s.handlingDays || s.transitDays ? clean({
|
|
128
|
+
"@type": "ShippingDeliveryTime",
|
|
129
|
+
handlingTime: range(s.handlingDays),
|
|
130
|
+
transitTime: range(s.transitDays)
|
|
131
|
+
}) : void 0
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/** `MerchantReturnPolicy`. Also usable on an Organization (`hasMerchantReturnPolicy`). */
|
|
135
|
+
function returnPolicyLd(r) {
|
|
136
|
+
if (!("country" in r)) return {
|
|
137
|
+
"@type": "MerchantReturnPolicy",
|
|
138
|
+
merchantReturnLink: r.url
|
|
139
|
+
};
|
|
140
|
+
const category = {
|
|
141
|
+
finite: "MerchantReturnFiniteReturnWindow",
|
|
142
|
+
unlimited: "MerchantReturnUnlimitedWindow",
|
|
143
|
+
notPermitted: "MerchantReturnNotPermitted"
|
|
144
|
+
}[r.category];
|
|
145
|
+
const fees = r.fees === void 0 ? {} : r.fees === "free" ? { returnFees: `${SCHEMA}FreeReturn` } : r.fees === "customer" ? { returnFees: `${SCHEMA}ReturnFeesCustomerResponsibility` } : {
|
|
146
|
+
returnFees: `${SCHEMA}ReturnShippingFees`,
|
|
147
|
+
returnShippingFeesAmount: {
|
|
148
|
+
"@type": "MonetaryAmount",
|
|
149
|
+
value: r.fees.amount,
|
|
150
|
+
currency: r.fees.currency
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const methods = {
|
|
154
|
+
mail: "ReturnByMail",
|
|
155
|
+
store: "ReturnInStore",
|
|
156
|
+
kiosk: "ReturnAtKiosk"
|
|
157
|
+
};
|
|
158
|
+
return clean({
|
|
159
|
+
"@type": "MerchantReturnPolicy",
|
|
160
|
+
applicableCountry: r.country,
|
|
161
|
+
returnPolicyCategory: SCHEMA + category,
|
|
162
|
+
merchantReturnDays: r.category === "finite" ? r.days : void 0,
|
|
163
|
+
returnMethod: r.method?.length ? r.method.map((m) => SCHEMA + methods[m]) : void 0,
|
|
164
|
+
merchantReturnLink: r.url,
|
|
165
|
+
...fees
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const OG_AVAILABILITY = {
|
|
169
|
+
InStock: "in stock",
|
|
170
|
+
OutOfStock: "out of stock",
|
|
171
|
+
PreOrder: "preorder",
|
|
172
|
+
PreSale: "preorder",
|
|
173
|
+
BackOrder: "available for order",
|
|
174
|
+
Discontinued: "discontinued",
|
|
175
|
+
LimitedAvailability: "in stock",
|
|
176
|
+
InStoreOnly: "in stock",
|
|
177
|
+
OnlineOnly: "in stock",
|
|
178
|
+
SoldOut: "out of stock"
|
|
179
|
+
};
|
|
180
|
+
const OG_CONDITION = {
|
|
181
|
+
NewCondition: "new",
|
|
182
|
+
UsedCondition: "used",
|
|
183
|
+
RefurbishedCondition: "refurbished",
|
|
184
|
+
DamagedCondition: "used"
|
|
185
|
+
};
|
|
186
|
+
/** Open Graph product tags: `product:price:amount`, `product:price:currency`, availability, condition, brand, item id. */
|
|
187
|
+
function productOpenGraph(p) {
|
|
188
|
+
const meta = [];
|
|
189
|
+
const first = p.offers === void 0 ? void 0 : Array.isArray(p.offers) ? p.offers[0] : p.offers;
|
|
190
|
+
if (first) {
|
|
191
|
+
const amount = "lowPrice" in first ? first.lowPrice : first.price;
|
|
192
|
+
meta.push({
|
|
193
|
+
property: "product:price:amount",
|
|
194
|
+
content: String(amount)
|
|
195
|
+
});
|
|
196
|
+
meta.push({
|
|
197
|
+
property: "product:price:currency",
|
|
198
|
+
content: first.currency
|
|
199
|
+
});
|
|
200
|
+
if (first.availability) meta.push({
|
|
201
|
+
property: "product:availability",
|
|
202
|
+
content: OG_AVAILABILITY[first.availability]
|
|
203
|
+
});
|
|
204
|
+
if ("condition" in first && first.condition) meta.push({
|
|
205
|
+
property: "product:condition",
|
|
206
|
+
content: OG_CONDITION[first.condition]
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (p.brand) meta.push({
|
|
210
|
+
property: "product:brand",
|
|
211
|
+
content: p.brand
|
|
212
|
+
});
|
|
213
|
+
if (p.sku) meta.push({
|
|
214
|
+
property: "product:retailer_item_id",
|
|
215
|
+
content: p.sku
|
|
216
|
+
});
|
|
217
|
+
if (p.groupId) meta.push({
|
|
218
|
+
property: "product:item_group_id",
|
|
219
|
+
content: p.groupId
|
|
220
|
+
});
|
|
221
|
+
return meta;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Everything a product page needs, as `seo()` options: `og:type` product, the product Open Graph tags
|
|
225
|
+
* and the Product JSON-LD. Spread it into your call:
|
|
226
|
+
*
|
|
227
|
+
* ```ts
|
|
228
|
+
* head: ({ loaderData: p }) => seo({ title: p.name, path: `/p/${p.slug}`, image: p.images[0], ...productSeo(p) })
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
function productSeo(p) {
|
|
232
|
+
return {
|
|
233
|
+
type: "product",
|
|
234
|
+
meta: productOpenGraph(p),
|
|
235
|
+
jsonLd: productJsonLd(p)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check a product against Google's product snippet and merchant listing requirements
|
|
240
|
+
* (as documented on 2026-10-22). Use it in tests or development; it does not throw.
|
|
241
|
+
*/
|
|
242
|
+
function checkProduct(p, options = {}) {
|
|
243
|
+
const errors = [];
|
|
244
|
+
const warnings = [];
|
|
245
|
+
const offers = p.offers === void 0 ? [] : Array.isArray(p.offers) ? p.offers : [p.offers];
|
|
246
|
+
if (!p.name?.trim()) errors.push("name is required");
|
|
247
|
+
if (!offers.length && !p.rating && !p.reviews?.length) errors.push("one of offers, rating or reviews is required for product snippets");
|
|
248
|
+
if (options.merchantListing) {
|
|
249
|
+
if (!p.image || Array.isArray(p.image) && !p.image.length) errors.push("image is required for merchant listings");
|
|
250
|
+
if (!offers.length) errors.push("offers is required for merchant listings");
|
|
251
|
+
}
|
|
252
|
+
if (!p.offers && (p.rating || p.reviews?.length)) warnings.push("offers is recommended: the Rich Results Test warns without it");
|
|
253
|
+
offers.forEach((o, i) => {
|
|
254
|
+
const at = offers.length > 1 ? `offers[${i}]` : "offers";
|
|
255
|
+
if ("lowPrice" in o) {
|
|
256
|
+
checkPrice(o.lowPrice, `${at}.lowPrice`, errors);
|
|
257
|
+
if (o.highPrice !== void 0) checkPrice(o.highPrice, `${at}.highPrice`, errors);
|
|
258
|
+
if (options.merchantListing) warnings.push(`${at}: use Offer rather than AggregateOffer for merchant listings and price-drop results`);
|
|
259
|
+
} else {
|
|
260
|
+
checkPrice(o.price, `${at}.price`, errors);
|
|
261
|
+
if (!o.availability) warnings.push(`${at}.availability is recommended`);
|
|
262
|
+
(o.shipping === void 0 ? [] : Array.isArray(o.shipping) ? o.shipping : [o.shipping]).forEach((s, j) => {
|
|
263
|
+
if (s.currency !== o.currency) errors.push(`${at}.shipping[${j}].currency must match the offer currency (${o.currency})`);
|
|
264
|
+
if (!s.handlingDays && !s.transitDays) errors.push(`${at}.shipping[${j}] needs handlingDays or transitDays (deliveryTime)`);
|
|
265
|
+
checkPrice(s.rate, `${at}.shipping[${j}].rate`, errors);
|
|
266
|
+
});
|
|
267
|
+
if (o.returnPolicy && "country" in o.returnPolicy && o.returnPolicy.category === "finite" && !o.returnPolicy.days) errors.push(`${at}.returnPolicy.days is required for a finite return window`);
|
|
268
|
+
}
|
|
269
|
+
if (!/^[A-Z]{3}$/.test(o.currency)) errors.push(`${at}.currency must be a 3-letter ISO 4217 code`);
|
|
270
|
+
});
|
|
271
|
+
if (p.rating) {
|
|
272
|
+
if (p.rating.count === void 0 && p.rating.reviewCount === void 0) errors.push("rating needs count or reviewCount");
|
|
273
|
+
const best = p.rating.best ?? 5;
|
|
274
|
+
if (p.rating.value > best || p.rating.value < (p.rating.worst ?? 1)) errors.push("rating.value is outside best/worst");
|
|
275
|
+
}
|
|
276
|
+
p.reviews?.forEach((r, i) => {
|
|
277
|
+
if (!r.author.trim()) errors.push(`reviews[${i}].author is required`);
|
|
278
|
+
if ((r.positiveNotes?.length ?? 0) + (r.negativeNotes?.length ?? 0) === 1) warnings.push(`reviews[${i}]: pros and cons need at least two statements in total`);
|
|
279
|
+
});
|
|
280
|
+
if (!p.brand) warnings.push("brand is recommended");
|
|
281
|
+
if (!p.gtin && !p.mpn && !p.sku) warnings.push("a gtin, mpn or sku is recommended");
|
|
282
|
+
if (!p.description) warnings.push("description is recommended");
|
|
283
|
+
return {
|
|
284
|
+
errors,
|
|
285
|
+
warnings
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/** Map your products to sitemap entries (with image entries) for a `createSitemap` source. */
|
|
289
|
+
function productSitemapEntries(items, options) {
|
|
290
|
+
const out = [];
|
|
291
|
+
for (const item of items) {
|
|
292
|
+
const lastmod = options.lastmod?.(item);
|
|
293
|
+
const images = options.images?.(item);
|
|
294
|
+
out.push(clean({
|
|
295
|
+
loc: options.path(item),
|
|
296
|
+
lastmod,
|
|
297
|
+
images: images?.length ? images : void 0,
|
|
298
|
+
changefreq: options.changefreq,
|
|
299
|
+
priority: options.priority
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
function checkPrice(value, at, errors) {
|
|
305
|
+
if (!(typeof value === "number" ? Number.isFinite(value) && value >= 0 : /^\d+(\.\d+)?$/.test(value))) errors.push(`${at} must be a non-negative number without currency symbols or separators`);
|
|
306
|
+
}
|
|
307
|
+
function dateOnly(value) {
|
|
308
|
+
return value instanceof Date ? value.toISOString().slice(0, 10) : value;
|
|
309
|
+
}
|
|
310
|
+
function clean(obj) {
|
|
311
|
+
const out = {};
|
|
312
|
+
for (const [k, v] of Object.entries(obj)) if (v !== void 0) out[k] = v;
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
export { aggregateOfferLd, aggregateRatingLd, checkProduct, jsonLd, offerLd, productJsonLd, productOpenGraph, productSeo, productSitemapEntries, returnPolicyLd, reviewLd, shippingLd };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { g as TrailingSlash } from "./sitemap-DbEixSCi.js";
|
|
2
|
+
/** Any JSON value allowed inside a JSON-LD document. */
|
|
3
|
+
type JsonLdValue = string | number | boolean | null | JsonLdValue[] | {
|
|
4
|
+
[key: string]: JsonLdValue | undefined;
|
|
5
|
+
};
|
|
6
|
+
/** A JSON-LD node: an object with `@type` (and usually `@context`). */
|
|
7
|
+
interface JsonLdObject {
|
|
8
|
+
'@context'?: string | Record<string, JsonLdValue>;
|
|
9
|
+
'@type'?: string | string[];
|
|
10
|
+
'@id'?: string;
|
|
11
|
+
'@graph'?: JsonLdObject[];
|
|
12
|
+
[key: string]: JsonLdValue | JsonLdObject | JsonLdObject[] | undefined;
|
|
13
|
+
}
|
|
14
|
+
type Ref = string | JsonLdObject;
|
|
15
|
+
interface PersonInput {
|
|
16
|
+
name: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
image?: string;
|
|
19
|
+
jobTitle?: string;
|
|
20
|
+
sameAs?: string[];
|
|
21
|
+
id?: string;
|
|
22
|
+
}
|
|
23
|
+
interface OrganizationInput {
|
|
24
|
+
name: string;
|
|
25
|
+
url?: string;
|
|
26
|
+
logo?: string;
|
|
27
|
+
sameAs?: string[];
|
|
28
|
+
email?: string;
|
|
29
|
+
telephone?: string;
|
|
30
|
+
id?: string;
|
|
31
|
+
/** `Organization` (default) or a subtype such as `Corporation` or `LocalBusiness`. */
|
|
32
|
+
type?: string;
|
|
33
|
+
}
|
|
34
|
+
interface WebSiteInput {
|
|
35
|
+
name: string;
|
|
36
|
+
url: string;
|
|
37
|
+
/** Enables the sitelinks search box: a URL with `{search_term_string}`, e.g. `https://acme.dev/search?q={search_term_string}`. */
|
|
38
|
+
searchUrlTemplate?: string;
|
|
39
|
+
publisher?: Ref;
|
|
40
|
+
inLanguage?: string;
|
|
41
|
+
id?: string;
|
|
42
|
+
}
|
|
43
|
+
interface ArticleLdInput {
|
|
44
|
+
/** `Article` (default), `BlogPosting`, `NewsArticle` or `TechArticle`. */
|
|
45
|
+
type?: 'Article' | 'BlogPosting' | 'NewsArticle' | 'TechArticle';
|
|
46
|
+
headline: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
url?: string;
|
|
49
|
+
image?: string | string[];
|
|
50
|
+
datePublished?: string | Date;
|
|
51
|
+
dateModified?: string | Date;
|
|
52
|
+
author?: Ref | Ref[];
|
|
53
|
+
publisher?: Ref;
|
|
54
|
+
keywords?: string[];
|
|
55
|
+
id?: string;
|
|
56
|
+
}
|
|
57
|
+
interface BreadcrumbItem {
|
|
58
|
+
name: string;
|
|
59
|
+
/** Absolute URL. Leave it out on the last (current) item if you like. */
|
|
60
|
+
url?: string;
|
|
61
|
+
}
|
|
62
|
+
interface FaqItem {
|
|
63
|
+
question: string;
|
|
64
|
+
/** Plain text or simple HTML. */
|
|
65
|
+
answer: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Typed builders for common schema.org types. Each returns a JSON-LD object with `@context`.
|
|
69
|
+
* Product, Offer, Review and return/shipping policies live in `@amjadiqbal/start-seo-kit/commerce`.
|
|
70
|
+
*/
|
|
71
|
+
declare const jsonLd: {
|
|
72
|
+
person(p: PersonInput): JsonLdObject;
|
|
73
|
+
organization(o: OrganizationInput): JsonLdObject;
|
|
74
|
+
website(w: WebSiteInput): JsonLdObject;
|
|
75
|
+
article(a: ArticleLdInput): JsonLdObject;
|
|
76
|
+
breadcrumbs(items: BreadcrumbItem[]): JsonLdObject;
|
|
77
|
+
faq(items: FaqItem[]): JsonLdObject;
|
|
78
|
+
/** Combine several nodes into one `@graph` document (their own `@context` is dropped). */
|
|
79
|
+
graph(...nodes: JsonLdObject[]): JsonLdObject;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* One entry of a route's `head().meta` array, in the shapes TanStack Router renders.
|
|
83
|
+
* The JSON-LD shape carries an optional `content?: undefined` so it stays assignable to
|
|
84
|
+
* React's `<meta>` props type, which Router's React adapter uses for `meta`.
|
|
85
|
+
*/
|
|
86
|
+
type MetaTag = {
|
|
87
|
+
title: string;
|
|
88
|
+
} | {
|
|
89
|
+
name: string;
|
|
90
|
+
content: string;
|
|
91
|
+
} | {
|
|
92
|
+
property: string;
|
|
93
|
+
content: string;
|
|
94
|
+
} | {
|
|
95
|
+
'script:ld+json': JsonLdObject;
|
|
96
|
+
content?: undefined;
|
|
97
|
+
};
|
|
98
|
+
/** One entry of a route's `head().links` array. */
|
|
99
|
+
type LinkTag = {
|
|
100
|
+
rel: string;
|
|
101
|
+
href: string;
|
|
102
|
+
hrefLang?: string;
|
|
103
|
+
type?: string;
|
|
104
|
+
title?: string;
|
|
105
|
+
};
|
|
106
|
+
interface HeadTags {
|
|
107
|
+
meta: MetaTag[];
|
|
108
|
+
links: LinkTag[];
|
|
109
|
+
}
|
|
110
|
+
type ImageInput = string | {
|
|
111
|
+
url: string;
|
|
112
|
+
width?: number;
|
|
113
|
+
height?: number;
|
|
114
|
+
alt?: string;
|
|
115
|
+
type?: string;
|
|
116
|
+
};
|
|
117
|
+
type RobotsInput = string | {
|
|
118
|
+
index?: boolean;
|
|
119
|
+
follow?: boolean;
|
|
120
|
+
noarchive?: boolean;
|
|
121
|
+
nosnippet?: boolean;
|
|
122
|
+
noimageindex?: boolean;
|
|
123
|
+
notranslate?: boolean;
|
|
124
|
+
maxSnippet?: number;
|
|
125
|
+
maxImagePreview?: 'none' | 'standard' | 'large';
|
|
126
|
+
maxVideoPreview?: number;
|
|
127
|
+
unavailableAfter?: string | Date;
|
|
128
|
+
};
|
|
129
|
+
type TwitterCard = 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
130
|
+
interface TwitterInput {
|
|
131
|
+
card?: TwitterCard;
|
|
132
|
+
/** `@handle` of the site. */
|
|
133
|
+
site?: string;
|
|
134
|
+
/** `@handle` of the author. */
|
|
135
|
+
creator?: string;
|
|
136
|
+
}
|
|
137
|
+
interface ArticleInput {
|
|
138
|
+
publishedTime?: string | Date;
|
|
139
|
+
modifiedTime?: string | Date;
|
|
140
|
+
expirationTime?: string | Date;
|
|
141
|
+
/** Author profile URL or name. Router keeps one tag per property, so only one author is emitted. */
|
|
142
|
+
author?: string;
|
|
143
|
+
section?: string;
|
|
144
|
+
}
|
|
145
|
+
interface SiteConfig {
|
|
146
|
+
/** Absolute origin (plus optional base path) of the site, e.g. `https://example.com`. */
|
|
147
|
+
url: string;
|
|
148
|
+
/** Site name, used for `og:site_name`. */
|
|
149
|
+
name?: string;
|
|
150
|
+
/** Title template; `%s` is replaced by the page title, e.g. `%s | Acme`. */
|
|
151
|
+
titleTemplate?: string;
|
|
152
|
+
/** Title used when a route passes none (not templated). */
|
|
153
|
+
defaultTitle?: string;
|
|
154
|
+
description?: string;
|
|
155
|
+
/** Default share image. Relative paths are resolved against `url`. */
|
|
156
|
+
image?: ImageInput;
|
|
157
|
+
/** `og:locale`, e.g. `en_US`. */
|
|
158
|
+
locale?: string;
|
|
159
|
+
twitter?: TwitterInput;
|
|
160
|
+
robots?: RobotsInput;
|
|
161
|
+
/** Match your router's `trailingSlash` option. Default `never`. */
|
|
162
|
+
trailingSlash?: TrailingSlash;
|
|
163
|
+
}
|
|
164
|
+
interface SeoInput {
|
|
165
|
+
title?: string;
|
|
166
|
+
/** Override the site template for this page, or `false` to use the title as is. */
|
|
167
|
+
titleTemplate?: string | false;
|
|
168
|
+
description?: string;
|
|
169
|
+
/** Path of this page (e.g. `/posts/${params.slug}`): sets the canonical link and `og:url`. */
|
|
170
|
+
path?: string;
|
|
171
|
+
/** Absolute or relative canonical URL, or `false` for none. Defaults to `path`. */
|
|
172
|
+
canonical?: string | false;
|
|
173
|
+
/** Share image, or `false` to drop the site default. */
|
|
174
|
+
image?: ImageInput | false;
|
|
175
|
+
/** `og:type`. Default `website`, or `article` when `article` is set. */
|
|
176
|
+
type?: string;
|
|
177
|
+
article?: ArticleInput;
|
|
178
|
+
robots?: RobotsInput;
|
|
179
|
+
/** Shorthand for `robots: { index: false, follow: true }`. */
|
|
180
|
+
noindex?: boolean;
|
|
181
|
+
/** hreflang alternates: `{ en: '/en/about', de: '/de/about', 'x-default': '/about' }`. */
|
|
182
|
+
alternates?: Record<string, string>;
|
|
183
|
+
locale?: string;
|
|
184
|
+
twitter?: TwitterInput;
|
|
185
|
+
/** Title for `og:title`/`twitter:title`. Default: the page title without the template. */
|
|
186
|
+
socialTitle?: string;
|
|
187
|
+
/** One or more JSON-LD objects (see the `jsonLd` helpers). */
|
|
188
|
+
jsonLd?: JsonLdObject | JsonLdObject[];
|
|
189
|
+
/** Extra meta tags, appended as given. */
|
|
190
|
+
meta?: MetaTag[];
|
|
191
|
+
/** Extra link tags, appended as given. */
|
|
192
|
+
links?: LinkTag[];
|
|
193
|
+
}
|
|
194
|
+
interface Seo {
|
|
195
|
+
/** Build the `meta` and `links` for a route's `head()`. */
|
|
196
|
+
(input?: SeoInput): HeadTags;
|
|
197
|
+
/** The site config with a normalized `url`. */
|
|
198
|
+
readonly site: Readonly<SiteConfig>;
|
|
199
|
+
/** Absolute URL of a page path, with the site's trailing-slash rule. */
|
|
200
|
+
url(path: string): string;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Create a typed head builder bound to your site's defaults.
|
|
204
|
+
*
|
|
205
|
+
* ```ts
|
|
206
|
+
* export const seo = defineSeo({ url: 'https://acme.dev', name: 'Acme', titleTemplate: '%s | Acme' })
|
|
207
|
+
* // in a route
|
|
208
|
+
* head: ({ loaderData }) => seo({ title: loaderData.post.title, path: `/posts/${loaderData.post.slug}` })
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
declare function defineSeo(config: SiteConfig): Seo;
|
|
212
|
+
/** Build head tags without a site config. Relative URLs are not allowed here. */
|
|
213
|
+
declare function seo(input: SeoInput & {
|
|
214
|
+
siteUrl?: string;
|
|
215
|
+
}): HeadTags;
|
|
216
|
+
/** Serialize robots directives, e.g. `{ index: false, maxImagePreview: 'large' }` -> `noindex, follow, max-image-preview:large`. */
|
|
217
|
+
declare function robotsContent(robots: RobotsInput | undefined): string | undefined;
|
|
218
|
+
export { jsonLd as C, WebSiteInput as S, FaqItem as _, MetaTag as a, OrganizationInput as b, SeoInput as c, TwitterInput as d, defineSeo as f, BreadcrumbItem as g, ArticleLdInput as h, LinkTag as i, SiteConfig as l, seo as m, HeadTags as n, RobotsInput as o, robotsContent as p, ImageInput as r, Seo as s, ArticleInput as t, TwitterCard as u, JsonLdObject as v, PersonInput as x, JsonLdValue as y };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { C as jsonLd, S as WebSiteInput, _ as FaqItem, a as MetaTag, b as OrganizationInput, c as SeoInput, d as TwitterInput, f as defineSeo, g as BreadcrumbItem, h as ArticleLdInput, i as LinkTag, l as SiteConfig, m as seo, n as HeadTags, o as RobotsInput, p as robotsContent, r as ImageInput, s as Seo, t as ArticleInput, u as TwitterCard, v as JsonLdObject, x as PersonInput, y as JsonLdValue } from "./head-D2pPoGNP.js";
|
|
2
|
+
import { _ as absoluteUrl, a as ServerHandler, c as SitemapImage, d as UrlOptions, f as createSitemap, g as TrailingSlash, h as sitemapXml, i as RoutesFromTreeOptions, l as SitemapIndexEntry, m as sitemapIndexXml, n as CreateSitemapOptions, o as SitemapEntry, p as routesFromTree, r as HandlerContext, s as SitemapHandlers, t as ChangeFreq, u as SitemapSource } from "./sitemap-DbEixSCi.js";
|
|
3
|
+
type Awaitable<T> = T | Promise<T>;
|
|
4
|
+
interface RobotsRule {
|
|
5
|
+
userAgent: string | string[];
|
|
6
|
+
allow?: string | string[];
|
|
7
|
+
disallow?: string | string[];
|
|
8
|
+
crawlDelay?: number;
|
|
9
|
+
}
|
|
10
|
+
interface RobotsConfig {
|
|
11
|
+
/** Default: one rule allowing everything for every user agent. */
|
|
12
|
+
rules?: RobotsRule[];
|
|
13
|
+
/** Sitemap paths or URLs. Paths need `siteUrl`. */
|
|
14
|
+
sitemaps?: string[];
|
|
15
|
+
siteUrl?: string;
|
|
16
|
+
/** Block every crawler (`Disallow: /`), e.g. on staging or preview deployments. Overrides `rules`. */
|
|
17
|
+
disallowAll?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Render a robots.txt body. */
|
|
20
|
+
export declare function robotsTxt(config?: RobotsConfig): string;
|
|
21
|
+
/**
|
|
22
|
+
* `GET` handler for a `/robots.txt` server route. Pass a function to decide per request
|
|
23
|
+
* (for example, `disallowAll` when the host is a preview deployment).
|
|
24
|
+
*/
|
|
25
|
+
export declare function createRobotsHandler(config: RobotsConfig | ((ctx: HandlerContext) => Awaitable<RobotsConfig>), init?: {
|
|
26
|
+
cacheControl?: string;
|
|
27
|
+
}): ServerHandler;
|
|
28
|
+
export { type ArticleInput, type ArticleLdInput, type BreadcrumbItem, type ChangeFreq, type CreateSitemapOptions, type FaqItem, type HandlerContext, type HeadTags, type ImageInput, type JsonLdObject, type JsonLdValue, type LinkTag, type MetaTag, type OrganizationInput, type PersonInput, type RobotsConfig, type RobotsInput, type RobotsRule, type RoutesFromTreeOptions, type Seo, type SeoInput, type ServerHandler, type SiteConfig, type SitemapEntry, type SitemapHandlers, type SitemapImage, type SitemapIndexEntry, type SitemapSource, type TrailingSlash, type TwitterCard, type TwitterInput, type UrlOptions, type WebSiteInput, absoluteUrl, createSitemap, defineSeo, jsonLd, robotsContent, routesFromTree, seo, sitemapIndexXml, sitemapXml };
|