nuxt-seo-utils 5.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.
Files changed (38) hide show
  1. package/README.md +61 -0
  2. package/dist/module.cjs +5 -0
  3. package/dist/module.d.mts +103 -0
  4. package/dist/module.d.ts +103 -0
  5. package/dist/module.json +13 -0
  6. package/dist/module.mjs +531 -0
  7. package/dist/runtime/nitro/middleware/redirectNitroOrigin.d.ts +2 -0
  8. package/dist/runtime/nitro/middleware/redirectNitroOrigin.js +18 -0
  9. package/dist/runtime/nitro/middleware/resolveImagesInPagesDir.dev.d.ts +2 -0
  10. package/dist/runtime/nitro/middleware/resolveImagesInPagesDir.dev.js +17 -0
  11. package/dist/runtime/nitro/tsconfig.json +3 -0
  12. package/dist/runtime/nuxt/composables/polyfills.d.ts +11 -0
  13. package/dist/runtime/nuxt/composables/polyfills.js +21 -0
  14. package/dist/runtime/nuxt/composables/useBreadcrumbItems.d.ts +95 -0
  15. package/dist/runtime/nuxt/composables/useBreadcrumbItems.js +98 -0
  16. package/dist/runtime/nuxt/logic/applyDefaults.d.ts +4 -0
  17. package/dist/runtime/nuxt/logic/applyDefaults.js +48 -0
  18. package/dist/runtime/nuxt/plugins/0.routeRules.server.d.ts +2 -0
  19. package/dist/runtime/nuxt/plugins/0.routeRules.server.js +21 -0
  20. package/dist/runtime/nuxt/plugins/1.absoluteImageUrls.server.d.ts +2 -0
  21. package/dist/runtime/nuxt/plugins/1.absoluteImageUrls.server.js +31 -0
  22. package/dist/runtime/nuxt/plugins/defaults.d.ts +2 -0
  23. package/dist/runtime/nuxt/plugins/defaults.js +20 -0
  24. package/dist/runtime/nuxt/plugins/defaultsWaitI18n.d.ts +2 -0
  25. package/dist/runtime/nuxt/plugins/defaultsWaitI18n.js +23 -0
  26. package/dist/runtime/nuxt/plugins/inferSeoMetaPlugin.d.ts +2 -0
  27. package/dist/runtime/nuxt/plugins/inferSeoMetaPlugin.js +9 -0
  28. package/dist/runtime/nuxt/plugins/siteConfig.d.ts +2 -0
  29. package/dist/runtime/nuxt/plugins/siteConfig.js +30 -0
  30. package/dist/runtime/nuxt/plugins/titles.d.ts +2 -0
  31. package/dist/runtime/nuxt/plugins/titles.js +29 -0
  32. package/dist/runtime/pure/breadcrumbs.d.ts +1 -0
  33. package/dist/runtime/pure/breadcrumbs.js +18 -0
  34. package/dist/runtime/types.d.ts +1 -0
  35. package/dist/runtime/types.js +0 -0
  36. package/dist/types.d.mts +1 -0
  37. package/dist/types.d.ts +1 -0
  38. package/package.json +83 -0
@@ -0,0 +1,531 @@
1
+ import { useNuxt, createResolver, addTemplate, defineNuxtModule, useLogger, addServerHandler, hasNuxtModule, addPlugin, addImports, addVitePlugin } from '@nuxt/kit';
2
+ import UnheadVite from '@unhead/addons/vite';
3
+ import { installNuxtSiteConfig } from 'nuxt-site-config-kit';
4
+ import { unpackMeta } from '@unhead/shared';
5
+ import fg from 'fast-glob';
6
+ import { relative, resolve, extname, dirname, basename } from 'pathe';
7
+ import fs from 'node:fs';
8
+ import { defu } from 'defu';
9
+ import { encodePath, joinURL } from 'ufo';
10
+ import escapeRE from 'escape-string-regexp';
11
+ import { readFile } from 'node:fs/promises';
12
+ import imageSize from 'image-size';
13
+
14
+ function extendNuxtConfigAppHeadSeoMeta(nuxt = useNuxt()) {
15
+ const seoMeta = nuxt.options.app?.seoMeta || nuxt.options.app?.head?.seoMeta || false;
16
+ if (!seoMeta)
17
+ return;
18
+ nuxt.options.app.head = nuxt.options.app.head || {};
19
+ nuxt.options.app.head = {
20
+ ...nuxt.options.app.head,
21
+ meta: [
22
+ ...nuxt.options.app.head.meta || [],
23
+ ...unpackMeta(seoMeta)
24
+ ]
25
+ };
26
+ delete nuxt.options.app.seoMeta;
27
+ return nuxt.options.app.head;
28
+ }
29
+
30
+ function extendTypes(module, template) {
31
+ const nuxt = useNuxt();
32
+ const { resolve } = createResolver(import.meta.url);
33
+ addTemplate({
34
+ filename: `module/${module}.d.ts`,
35
+ getContents: async () => {
36
+ const typesPath = relative(resolve(nuxt.options.rootDir, nuxt.options.buildDir, "module"), resolve("runtime/types"));
37
+ const s = await template({ typesPath });
38
+ return `// Generated by ${module}
39
+ ${s}
40
+ export {}
41
+ `;
42
+ }
43
+ });
44
+ nuxt.hooks.hook("prepare:types", ({ references }) => {
45
+ references.push({ path: resolve(nuxt.options.buildDir, `module/${module}.d.ts`) });
46
+ });
47
+ }
48
+
49
+ function extendNuxtConfigAppHeadTypes(nuxt = useNuxt()) {
50
+ extendTypes("nuxt-seo-utils.assets", async () => {
51
+ const paths = (await fg(["**/*"], { cwd: resolve(nuxt.options.srcDir, "public") })).map((p) => `/${p}`);
52
+ const jsPaths = paths.filter((p) => p.endsWith(".js") || p.endsWith(".mjs"));
53
+ return `
54
+ declare module '#app' {
55
+ import { HeadEntry, HeadTag } from '@unhead/schema'
56
+
57
+ interface RuntimeNuxtHooks {
58
+ 'head:tags': (tag: HeadTag[]) => Promise<void> | void
59
+ 'head:entries': (entries: HeadEntry[]) => Promise<void> | void
60
+ }
61
+ }
62
+ declare module '@unhead/schema' {
63
+
64
+ type PublicFiles = ${[...paths.map((p) => `'${p}'`), "(string & Record<never, never>)"].join(" | ")}
65
+ type JsFiles = ${[...jsPaths.map((p) => `'${p}'`), "(string & Record<never, never>)"].join(" | ")}
66
+
67
+ interface SchemaAugmentations {
68
+ link: import('@unhead/schema').UserTagConfigWithoutInnerContent & {
69
+ href: PublicFiles
70
+ }
71
+ script: import('@unhead/schema').TagUserProperties & {
72
+ src: JsFiles
73
+ }
74
+ }
75
+ }`;
76
+ });
77
+ }
78
+
79
+ const MetaTagFileGlobs = [
80
+ "**/*og-image.{png,jpg,jpeg,gif}",
81
+ "**/*opengraph-image.{png,jpg,jpeg,gif}",
82
+ "**/*twitter-image.{png,jpg,jpeg,gif}",
83
+ "**/*icon.{ico,jpg,jpeg,png,svg}",
84
+ "**/*icon-*.{ico,jpg,jpeg,png,svg}",
85
+ "**/*apple-icon.{jpg,jpeg,png}",
86
+ "**/*apple-touch.{jpg,jpeg,png}",
87
+ "**/*apple-touch-icon.{jpg,jpeg,png}"
88
+ ];
89
+
90
+ function generateNuxtPageFromFile(file, pagesDir) {
91
+ const segments = relative(pagesDir, file).replace(new RegExp(`${escapeRE(extname(file))}$`), "").split("/");
92
+ const route = {
93
+ name: "",
94
+ path: "",
95
+ file,
96
+ children: []
97
+ };
98
+ let parent = [];
99
+ for (let i = 0; i < segments.length; i++) {
100
+ const segment = segments[i];
101
+ const tokens = parseSegment(segment);
102
+ const segmentName = tokens.map(({ value }) => value).join("");
103
+ route.name += (route.name && "-") + segmentName;
104
+ const child = parent.find((parentRoute) => parentRoute.name === route.name && !parentRoute.path.endsWith("(.*)*"));
105
+ if (child && child.children) {
106
+ parent = child.children;
107
+ route.path = "";
108
+ } else if (segmentName === "index" && !route.path) {
109
+ route.path += "/";
110
+ } else if (segmentName !== "index") {
111
+ route.path += getRoutePath(tokens);
112
+ }
113
+ }
114
+ if (route.name)
115
+ route.name = route.name.replace(/-index$/, "");
116
+ return route;
117
+ }
118
+ function getRoutePath(tokens) {
119
+ return tokens.reduce((path, token) => {
120
+ return path + (token.type === 2 /* optional */ ? `:${token.value}?` : token.type === 1 /* dynamic */ ? `:${token.value}` : token.type === 3 /* catchall */ ? `:${token.value}(.*)*` : encodePath(token.value));
121
+ }, "/");
122
+ }
123
+ const PARAM_CHAR_RE = /[\w.]/;
124
+ function parseSegment(segment) {
125
+ let state = 0 /* initial */;
126
+ let i = 0;
127
+ let buffer = "";
128
+ const tokens = [];
129
+ function consumeBuffer() {
130
+ if (!buffer)
131
+ return;
132
+ if (state === 0 /* initial */)
133
+ throw new Error("wrong state");
134
+ tokens.push({
135
+ type: state === 1 /* static */ ? 0 /* static */ : state === 2 /* dynamic */ ? 1 /* dynamic */ : state === 3 /* optional */ ? 2 /* optional */ : 3 /* catchall */,
136
+ value: buffer
137
+ });
138
+ buffer = "";
139
+ }
140
+ while (i < segment.length) {
141
+ const c = segment[i];
142
+ switch (state) {
143
+ case 0 /* initial */:
144
+ buffer = "";
145
+ if (c === "[") {
146
+ state = 2 /* dynamic */;
147
+ } else {
148
+ i--;
149
+ state = 1 /* static */;
150
+ }
151
+ break;
152
+ case 1 /* static */:
153
+ if (c === "[") {
154
+ consumeBuffer();
155
+ state = 2 /* dynamic */;
156
+ } else {
157
+ buffer += c;
158
+ }
159
+ break;
160
+ case 4 /* catchall */:
161
+ case 2 /* dynamic */:
162
+ case 3 /* optional */:
163
+ if (buffer === "...") {
164
+ buffer = "";
165
+ state = 4 /* catchall */;
166
+ }
167
+ if (c === "[" && state === 2 /* dynamic */)
168
+ state = 3 /* optional */;
169
+ if (c === "]" && (state !== 3 /* optional */ || buffer[buffer.length - 1] === "]")) {
170
+ if (!buffer)
171
+ throw new Error("Empty param");
172
+ else
173
+ consumeBuffer();
174
+ state = 0 /* initial */;
175
+ } else if (PARAM_CHAR_RE.test(c)) {
176
+ buffer += c;
177
+ } else ;
178
+ break;
179
+ }
180
+ i++;
181
+ }
182
+ if (state === 2 /* dynamic */)
183
+ throw new Error(`Unfinished param "${buffer}"`);
184
+ consumeBuffer();
185
+ return tokens;
186
+ }
187
+
188
+ function hasLinkRel(input, rel) {
189
+ return input.link?.some((link) => link.rel === rel);
190
+ }
191
+ function hasMetaProperty(input, property) {
192
+ return input.meta?.some((meta) => meta.property === property);
193
+ }
194
+ async function getImageMeta(base, path, isIcon = false) {
195
+ const absolutePath = resolve(base, path);
196
+ const file = absolutePath.split("/").pop();
197
+ const keyword = file.split(".")[0];
198
+ let ext = absolutePath.split(".").pop();
199
+ if (ext === "jpg")
200
+ ext = "jpeg";
201
+ const { width, height } = await getImageDimensions(absolutePath);
202
+ const payload = {
203
+ type: ext === "svg" ? `image/svg+xml` : `image/${ext}`
204
+ };
205
+ if (!isIcon) {
206
+ payload.width = width;
207
+ payload.height = height;
208
+ const altTextPath = resolve(dirname(absolutePath), `${keyword}.alt.txt`);
209
+ if (fs.existsSync(altTextPath)) {
210
+ payload.alt = fs.readFileSync(altTextPath, "utf8");
211
+ payload.alt = payload.alt.replace(/\n/g, " ").trim();
212
+ }
213
+ } else {
214
+ if (path.includes(".dark") || path.includes("-dark"))
215
+ payload.media = "(prefers-color-scheme: dark)";
216
+ else if (path.includes(".light") || path.includes("-light"))
217
+ payload.media = "(prefers-color-scheme: light)";
218
+ if (ext !== "svg") {
219
+ payload.sizes = `${width}x${height}`;
220
+ } else {
221
+ payload.sizes = "any";
222
+ }
223
+ }
224
+ return payload;
225
+ }
226
+ async function getImageDimensions(absolutePath) {
227
+ const buffer = await readFile(absolutePath);
228
+ return imageSize(buffer);
229
+ }
230
+
231
+ async function generateTagsFromPageDirImages(nuxt = useNuxt()) {
232
+ const pagesDir = resolve(nuxt.options.rootDir, nuxt.options.dir.pages);
233
+ const files = await fg(MetaTagFileGlobs, { cwd: pagesDir, onlyFiles: true });
234
+ const appendRouteRules = {};
235
+ const devMiddlewareMap = {};
236
+ const nitroOutputMap = [];
237
+ for (const file of files) {
238
+ const fileName = basename(file);
239
+ const keyword = fileName.split(".")[0];
240
+ let { path } = generateNuxtPageFromFile(resolve(pagesDir, dirname(file)), pagesDir);
241
+ const meta = await getImageMeta(pagesDir, file);
242
+ if (path.endsWith("/_dir"))
243
+ path = path.replace(/\/_dir$/, "");
244
+ const src = joinURL(path, fileName);
245
+ if (["icon", "apple-touch-icon", "apple-icon"].includes(keyword) || keyword.startsWith("icon-")) {
246
+ appendRouteRules[`${path}/**`] = {
247
+ head: {
248
+ link: [
249
+ { rel: keyword, type: meta.type, href: src, sizes: meta.sizes }
250
+ ]
251
+ }
252
+ };
253
+ } else {
254
+ appendRouteRules[`${path}/**`] = {
255
+ seoMeta: {
256
+ [["opengraph-image", "og-image"].includes(keyword) ? "ogImage" : "twitterImage"]: [{ url: src, ...meta, sizes: void 0 }]
257
+ }
258
+ };
259
+ }
260
+ devMiddlewareMap[src] = resolve(pagesDir, file);
261
+ nitroOutputMap.push({
262
+ src: resolve(pagesDir, file),
263
+ dest: src.slice(1)
264
+ // remove leading slash
265
+ });
266
+ }
267
+ nuxt.options.routeRules = defu(appendRouteRules, nuxt.options.routeRules);
268
+ nuxt.options.nitro.routeRules = defu(appendRouteRules, nuxt.options.nitro.routeRules);
269
+ if (nuxt.options.dev) {
270
+ nuxt.hooks.hook("nitro:config", async (nitroConfig) => {
271
+ nitroConfig.virtual["#nuxt-seo-utils/pageDirImages"] = `export const fileMapping = ${JSON.stringify(devMiddlewareMap)}`;
272
+ });
273
+ }
274
+ nuxt.hooks.hook("nitro:build:public-assets", (_nitro) => {
275
+ const publicDir = resolve(_nitro.options.output.dir, _nitro.options.output.publicDir);
276
+ nitroOutputMap.forEach(({ src, dest }) => {
277
+ const resolvedDest = resolve(publicDir, dest);
278
+ const destFolder = dirname(resolvedDest);
279
+ if (!fs.existsSync(destFolder))
280
+ fs.mkdirSync(destFolder, { recursive: true });
281
+ if (!fs.existsSync(resolvedDest))
282
+ fs.copyFileSync(src, resolvedDest);
283
+ });
284
+ });
285
+ }
286
+
287
+ async function generateTagsFromPublicFiles(nuxt = useNuxt()) {
288
+ const publicDirPath = resolve(nuxt.options.rootDir, nuxt.options.dir.public);
289
+ const rootPublicFiles = (await fg(MetaTagFileGlobs, { cwd: publicDirPath, onlyFiles: true, deep: 1 })).map((file) => basename(file));
290
+ const headConfig = defu(nuxt.options.app.head, {
291
+ link: [],
292
+ meta: []
293
+ });
294
+ if (!hasLinkRel(headConfig, "icon")) {
295
+ if (rootPublicFiles.includes("favicon.ico") && nuxt.options.app.baseURL !== "/") {
296
+ headConfig.link.push({
297
+ rel: "icon",
298
+ href: joinURL(nuxt.options.app.baseURL, "favicon.ico"),
299
+ sizes: "any"
300
+ });
301
+ }
302
+ const isIcon = (file) => file.includes("icon") && !file.endsWith(".ico");
303
+ const isAppleTouchIcon = (file) => file.startsWith("apple-icon.") || file.startsWith("apple-touch-icon.") || file.startsWith("apple-touch.");
304
+ headConfig.link.push(
305
+ ...await Promise.all([
306
+ ...rootPublicFiles.filter((file) => isIcon(file) && !isAppleTouchIcon(file)).sort().map(async (iconFile) => {
307
+ const meta = await getImageMeta(publicDirPath, iconFile, true);
308
+ return {
309
+ rel: "icon",
310
+ href: joinURL(nuxt.options.app.baseURL, iconFile),
311
+ ...meta
312
+ };
313
+ }),
314
+ ...rootPublicFiles.filter((file) => isAppleTouchIcon(file)).sort().map(async (appleIconFile) => {
315
+ const meta = await getImageMeta(publicDirPath, appleIconFile, true);
316
+ return {
317
+ rel: "apple-touch-icon",
318
+ href: joinURL(nuxt.options.app.baseURL, appleIconFile),
319
+ ...meta
320
+ };
321
+ })
322
+ ])
323
+ );
324
+ }
325
+ let hasTwitterImage = hasMetaProperty(headConfig, "twitter:image");
326
+ if (!hasTwitterImage) {
327
+ const twitterImageFiles = rootPublicFiles.filter((file) => file.startsWith("twitter-image.")).sort();
328
+ if (twitterImageFiles.length) {
329
+ headConfig.meta.push(
330
+ ...(await Promise.all(twitterImageFiles.map(async (twitterImageFile) => {
331
+ const dimensions = await getImageDimensions(resolve(publicDirPath, twitterImageFile));
332
+ return unpackMeta({
333
+ twitterImage: {
334
+ url: twitterImageFile,
335
+ width: dimensions.width,
336
+ height: dimensions.height
337
+ }
338
+ });
339
+ }))).flat()
340
+ );
341
+ hasTwitterImage = true;
342
+ }
343
+ }
344
+ if (!hasMetaProperty(headConfig, "og:image")) {
345
+ const ogImageFiles = rootPublicFiles.filter((file) => file.startsWith("og-image.") || file.startsWith("og.")).sort();
346
+ if (ogImageFiles.length) {
347
+ headConfig.meta.push(
348
+ ...(await Promise.all(ogImageFiles.map(async (src) => {
349
+ const meta = await getImageMeta(publicDirPath, src, false);
350
+ delete meta.sizes;
351
+ const seoMeta = {
352
+ ogImage: {
353
+ url: src,
354
+ ...meta
355
+ }
356
+ };
357
+ if (!hasTwitterImage) {
358
+ seoMeta.twitterImage = {
359
+ url: src,
360
+ ...meta
361
+ };
362
+ }
363
+ return unpackMeta(seoMeta);
364
+ }))).flat()
365
+ );
366
+ }
367
+ }
368
+ nuxt.options.app.head = headConfig;
369
+ }
370
+
371
+ function setupNuxtConfigAppHeadWithMoreDefaults(nuxt = useNuxt()) {
372
+ const headConfig = nuxt.options.app.head;
373
+ headConfig.link = headConfig.link || [];
374
+ headConfig.htmlAttrs = headConfig.htmlAttrs || {};
375
+ headConfig.link = headConfig.link || [];
376
+ headConfig.meta = headConfig.meta || [];
377
+ if (!hasMetaProperty(headConfig, "og:type")) {
378
+ headConfig.meta.push({
379
+ property: "og:type",
380
+ content: "website"
381
+ });
382
+ }
383
+ nuxt.options.app.head = headConfig;
384
+ }
385
+
386
+ const module = defineNuxtModule({
387
+ meta: {
388
+ name: "nuxt-seo-utils",
389
+ configKey: "seo",
390
+ compatibility: {
391
+ nuxt: ">=3.6.1",
392
+ bridge: false
393
+ }
394
+ },
395
+ defaults: {
396
+ enabled: true,
397
+ debug: false,
398
+ redirectToCanonicalSiteUrl: false,
399
+ automaticDefaults: true,
400
+ fallbackTitle: true,
401
+ metaDataFiles: true,
402
+ mergeWithSiteConfig: true,
403
+ extendRouteRules: true,
404
+ fixRequiredAbsoluteMetaTagsLinks: true,
405
+ extendNuxtConfigAppHeadSeoMeta: true,
406
+ treeShakeUseSeoMeta: true,
407
+ extendNuxtConfigAppHeadTypes: true,
408
+ setupNuxtConfigAppHeadWithMoreDefaults: true,
409
+ automaticOgAndTwitterTags: true
410
+ },
411
+ async setup(config, nuxt) {
412
+ const logger = useLogger("nuxt-seo-utils");
413
+ logger.level = config.debug || nuxt.options.debug ? 4 : 3;
414
+ if (config.enabled === false) {
415
+ logger.debug("The module is disabled, skipping setup.");
416
+ return;
417
+ }
418
+ const { resolve } = createResolver(import.meta.url);
419
+ await installNuxtSiteConfig();
420
+ if (config.metaDataFiles) {
421
+ await generateTagsFromPublicFiles();
422
+ await generateTagsFromPageDirImages();
423
+ if (nuxt.options.dev) {
424
+ addServerHandler({
425
+ middleware: true,
426
+ handler: resolve("runtime/nitro/middleware/resolveImagesInPagesDir.dev")
427
+ });
428
+ }
429
+ }
430
+ if (config.automaticDefaults) {
431
+ if (hasNuxtModule("@nuxtjs/i18n")) {
432
+ addPlugin({
433
+ src: resolve(`./runtime/nuxt/plugins/defaultsWaitI18n`)
434
+ });
435
+ } else {
436
+ addPlugin({
437
+ src: resolve(`./runtime/nuxt/plugins/defaults`)
438
+ });
439
+ }
440
+ }
441
+ if (config.fallbackTitle) {
442
+ addPlugin({
443
+ src: resolve("./runtime/nuxt/plugins/titles")
444
+ });
445
+ }
446
+ if (!hasNuxtModule("@nuxtjs/i18n")) {
447
+ addImports({
448
+ from: resolve(`./runtime/nuxt/composables/polyfills`),
449
+ name: "useI18n"
450
+ });
451
+ }
452
+ addImports({
453
+ from: resolve(`./runtime/nuxt/composables/useBreadcrumbItems`),
454
+ name: "useBreadcrumbItems"
455
+ });
456
+ const polyfills = [
457
+ // @ts-expect-error runtime type
458
+ ...!hasNuxtModule("nuxt-schema-org") || nuxt.options.schemaOrg?.enable === false ? [
459
+ "useSchemaOrg",
460
+ "defineWebSite",
461
+ "defineWebPage",
462
+ "defineBreadcrumb"
463
+ ] : []
464
+ ];
465
+ polyfills.forEach((name) => {
466
+ addImports({
467
+ from: resolve("./runtime/nuxt/composables/polyfills"),
468
+ name
469
+ });
470
+ });
471
+ nuxt.options.experimental.headNext = true;
472
+ if (!nuxt.options.dev && config.redirectToCanonicalSiteUrl) {
473
+ addServerHandler({
474
+ handler: resolve("./runtime/nitro/middleware/redirect"),
475
+ middleware: true
476
+ });
477
+ }
478
+ nuxt.options.runtimeConfig.public["nuxt-seo"] = {
479
+ canonicalQueryWhitelist: config.canonicalQueryWhitelist || [
480
+ "page",
481
+ "sort",
482
+ "filter",
483
+ "search",
484
+ "q",
485
+ "category",
486
+ "tag"
487
+ ]
488
+ };
489
+ if (config.extendNuxtConfigAppHeadSeoMeta)
490
+ extendNuxtConfigAppHeadSeoMeta();
491
+ if (config.setupNuxtConfigAppHeadWithMoreDefaults)
492
+ setupNuxtConfigAppHeadWithMoreDefaults(nuxt);
493
+ if (config.extendNuxtConfigAppHeadTypes)
494
+ extendNuxtConfigAppHeadTypes();
495
+ extendTypes("nuxt-seo-utils", async () => {
496
+ return `
497
+ declare module 'nitropack' {
498
+ interface NitroRouteRules {
499
+ seoMeta?: import('@unhead/schema').UseSeoMetaInput
500
+ head?: import('@unhead/schema').Head
501
+ }
502
+ interface NitroRouteConfig {
503
+ seoMeta?: import('@unhead/schema').UseSeoMetaInput
504
+ head?: import('@unhead/schema').Head
505
+ }
506
+ }
507
+
508
+ declare module '@nuxt/schema' {
509
+ interface MetaObjectRaw { seoMeta?: import('@unhead/schema').UseSeoMetaInput }
510
+ }
511
+
512
+ declare module 'nuxt/schema' {
513
+ interface MetaObjectRaw { seoMeta?: import('@unhead/schema').UseSeoMetaInput }
514
+ }
515
+ `;
516
+ });
517
+ const runtimeDir = resolve("./runtime/nuxt");
518
+ if (config.treeShakeUseSeoMeta)
519
+ addVitePlugin(UnheadVite());
520
+ if (config.automaticOgAndTwitterTags)
521
+ addPlugin({ src: resolve(runtimeDir, "plugins", "inferSeoMetaPlugin") });
522
+ if (config.mergeWithSiteConfig)
523
+ addPlugin({ src: resolve(runtimeDir, "plugins", "siteConfig") });
524
+ if (config.extendRouteRules)
525
+ addPlugin({ src: resolve(runtimeDir, "plugins", "0.routeRules.server"), mode: "server" });
526
+ if (config.fixRequiredAbsoluteMetaTagsLinks)
527
+ addPlugin({ src: resolve(runtimeDir, "plugins", "1.absoluteImageUrls.server"), mode: "server" });
528
+ }
529
+ });
530
+
531
+ export { module as default };
@@ -0,0 +1,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void> | undefined>;
2
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import { useNitroOrigin, useSiteConfig } from "#imports";
2
+ import { defineEventHandler, sendRedirect, setHeader } from "h3";
3
+ import { joinURL } from "ufo";
4
+ export default defineEventHandler((e) => {
5
+ const siteConfig = useSiteConfig(e);
6
+ if (siteConfig.url) {
7
+ const siteConfigHostName = new URL(e.path, siteConfig.url).hostname;
8
+ const origin = useNitroOrigin(e);
9
+ const originHostname = new URL(e.path, origin).hostname;
10
+ if (e.headers.get("x-nuxt-seo-redirected")) {
11
+ return;
12
+ }
13
+ if (originHostname !== siteConfigHostName && originHostname !== new URL(e.path, origin).hostname) {
14
+ setHeader(e, "x-nuxt-seo-redirected", "true");
15
+ return sendRedirect(e, joinURL(siteConfig.url, e.path), 301);
16
+ }
17
+ }
18
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import fs from "node:fs";
2
+ import { defineEventHandler } from "#imports";
3
+ import { fileMapping } from "#nuxt-seo-utils/pageDirImages";
4
+ import { setHeader } from "h3";
5
+ import { parseURL } from "ufo";
6
+ export default defineEventHandler(async (e) => {
7
+ const path = parseURL(e.path).pathname;
8
+ if (fileMapping[path]) {
9
+ if (path.endsWith(".svg"))
10
+ setHeader(e, "Content-Type", "image/svg+xml");
11
+ else if (path.endsWith(".png"))
12
+ setHeader(e, "Content-Type", "image/png");
13
+ else if (path.endsWith(".jpg") || path.endsWith(".jpeg"))
14
+ setHeader(e, "Content-Type", "image/jpeg");
15
+ return fs.readFileSync(fileMapping[path]);
16
+ }
17
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../../.nuxt/tsconfig.server.json"
3
+ }
@@ -0,0 +1,11 @@
1
+ export declare function useSchemaOrg(): void;
2
+ export declare function defineWebSite(): void;
3
+ export declare function defineWebPage(): void;
4
+ export declare function defineBreadcrumb(): void;
5
+ export declare function useI18n(): {
6
+ t: (_: string, fallback: string, options: any) => string;
7
+ te: (_: string) => boolean;
8
+ strategy: string;
9
+ defaultLocale: import("vue").Ref<any, any>;
10
+ locale: import("vue").Ref<any, any>;
11
+ };
@@ -0,0 +1,21 @@
1
+ import { useSiteConfig } from "#imports";
2
+ import { ref } from "vue";
3
+ export function useSchemaOrg() {
4
+ }
5
+ export function defineWebSite() {
6
+ }
7
+ export function defineWebPage() {
8
+ }
9
+ export function defineBreadcrumb() {
10
+ }
11
+ export function useI18n() {
12
+ const siteConfig = useSiteConfig();
13
+ return {
14
+ // eslint-disable-next-line unused-imports/no-unused-vars
15
+ t: (_, fallback, options) => fallback,
16
+ te: (_) => false,
17
+ strategy: "no_prefix",
18
+ defaultLocale: ref(siteConfig.defaultLocale || "en"),
19
+ locale: ref(siteConfig.currentLocale || siteConfig.defaultLocale || "en")
20
+ };
21
+ }