@intlayer/docs 6.1.4 → 6.1.6-canary.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 (75) hide show
  1. package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +1366 -75
  2. package/blog/ar/nextjs-multilingual-seo-comparison.md +364 -0
  3. package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +1288 -72
  4. package/blog/de/nextjs-multilingual-seo-comparison.md +362 -0
  5. package/blog/en/intlayer_with_next-i18next.mdx +431 -0
  6. package/blog/en/intlayer_with_next-intl.mdx +335 -0
  7. package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +583 -336
  8. package/blog/en/nextjs-multilingual-seo-comparison.md +360 -0
  9. package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +1144 -37
  10. package/blog/en-GB/nextjs-multilingual-seo-comparison.md +360 -0
  11. package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +1236 -64
  12. package/blog/es/nextjs-multilingual-seo-comparison.md +363 -0
  13. package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +1142 -75
  14. package/blog/fr/nextjs-multilingual-seo-comparison.md +362 -0
  15. package/blog/hi/nextjs-multilingual-seo-comparison.md +363 -0
  16. package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +1130 -55
  17. package/blog/it/nextjs-multilingual-seo-comparison.md +363 -0
  18. package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +1150 -76
  19. package/blog/ja/nextjs-multilingual-seo-comparison.md +362 -0
  20. package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +1139 -73
  21. package/blog/ko/nextjs-multilingual-seo-comparison.md +362 -0
  22. package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +1143 -76
  23. package/blog/pt/nextjs-multilingual-seo-comparison.md +362 -0
  24. package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +1150 -74
  25. package/blog/ru/nextjs-multilingual-seo-comparison.md +370 -0
  26. package/blog/tr/next-i18next_vs_next-intl_vs_intlayer.md +2 -0
  27. package/blog/tr/nextjs-multilingual-seo-comparison.md +362 -0
  28. package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +1152 -75
  29. package/blog/zh/nextjs-multilingual-seo-comparison.md +394 -0
  30. package/dist/cjs/generated/blog.entry.cjs +16 -0
  31. package/dist/cjs/generated/blog.entry.cjs.map +1 -1
  32. package/dist/cjs/generated/docs.entry.cjs +16 -0
  33. package/dist/cjs/generated/docs.entry.cjs.map +1 -1
  34. package/dist/esm/generated/blog.entry.mjs +16 -0
  35. package/dist/esm/generated/blog.entry.mjs.map +1 -1
  36. package/dist/esm/generated/docs.entry.mjs +16 -0
  37. package/dist/esm/generated/docs.entry.mjs.map +1 -1
  38. package/dist/types/generated/blog.entry.d.ts +1 -0
  39. package/dist/types/generated/blog.entry.d.ts.map +1 -1
  40. package/dist/types/generated/docs.entry.d.ts +1 -0
  41. package/dist/types/generated/docs.entry.d.ts.map +1 -1
  42. package/docs/ar/component_i18n.md +186 -0
  43. package/docs/ar/vs_code_extension.md +48 -109
  44. package/docs/de/component_i18n.md +186 -0
  45. package/docs/de/vs_code_extension.md +46 -107
  46. package/docs/en/component_i18n.md +186 -0
  47. package/docs/en/interest_of_intlayer.md +2 -2
  48. package/docs/en/intlayer_with_nextjs_14.md +18 -1
  49. package/docs/en/intlayer_with_nextjs_15.md +18 -1
  50. package/docs/en/vs_code_extension.md +24 -114
  51. package/docs/en-GB/component_i18n.md +186 -0
  52. package/docs/en-GB/vs_code_extension.md +42 -103
  53. package/docs/es/component_i18n.md +182 -0
  54. package/docs/es/vs_code_extension.md +53 -114
  55. package/docs/fr/component_i18n.md +186 -0
  56. package/docs/fr/vs_code_extension.md +50 -111
  57. package/docs/hi/component_i18n.md +186 -0
  58. package/docs/hi/vs_code_extension.md +49 -110
  59. package/docs/it/component_i18n.md +186 -0
  60. package/docs/it/vs_code_extension.md +50 -111
  61. package/docs/ja/component_i18n.md +186 -0
  62. package/docs/ja/vs_code_extension.md +50 -111
  63. package/docs/ko/component_i18n.md +186 -0
  64. package/docs/ko/vs_code_extension.md +48 -109
  65. package/docs/pt/component_i18n.md +186 -0
  66. package/docs/pt/vs_code_extension.md +46 -107
  67. package/docs/ru/component_i18n.md +186 -0
  68. package/docs/ru/vs_code_extension.md +48 -109
  69. package/docs/tr/component_i18n.md +186 -0
  70. package/docs/tr/vs_code_extension.md +54 -115
  71. package/docs/zh/component_i18n.md +186 -0
  72. package/docs/zh/vs_code_extension.md +51 -105
  73. package/package.json +11 -11
  74. package/src/generated/blog.entry.ts +16 -0
  75. package/src/generated/docs.entry.ts +16 -0
@@ -0,0 +1,360 @@
1
+ ---
2
+ createdAt: 2025-09-28
3
+ updatedAt: 2025-09-28
4
+ title: SEO and i18n in Next.js
5
+ description: Learn how to set up multilingual SEO in your Next.js app using next-intl, next-i18next, and Intlayer.
6
+ keywords:
7
+ - Intlayer
8
+ - SEO
9
+ - Internationalization
10
+ - Next.js
11
+ - i18n
12
+ - JavaScript
13
+ - React
14
+ - next-intl
15
+ - next-i18next
16
+ slugs:
17
+ - blog
18
+ - blog-seo-i18n-nextjs
19
+ ---
20
+
21
+ # SEO and i18n in Next.js: Translating is not enough
22
+
23
+ When developers think about internationalization (i18n), the first reflex is often: _translate the content_. But people usually forget that the main goal of internationalization is to make your website more visible to the world.
24
+ If your multilingual Next.js app doesn’t tell search engines how to crawl and understand your different language versions, most of your effort may go unnoticed.
25
+
26
+ In this blog, we’ll explore **why i18n is an SEO superpower** and how to implement it correctly in Next.js with `next-intl`, `next-i18next`, and `Intlayer`.
27
+
28
+ ---
29
+
30
+ ## Why SEO and i18n
31
+
32
+ Adding languages isn’t just about UX. It’s also a powerful lever for **organic visibility**. Here’s why:
33
+
34
+ 1. **Better discoverability:** Search engines index localized versions and rank them for users searching in their native language.
35
+ 2. **Avoid duplicate content:** Proper canonical and alternate tags tell crawlers which page belongs to which locale.
36
+ 3. **Better UX:** Visitors land on the right version of your site immediately.
37
+ 4. **Competitive advantage:** Few sites implement multilingual SEO well which means you can stand out.
38
+
39
+ ---
40
+
41
+ ## Best Practices for Multilingual SEO in Next.js
42
+
43
+ Here’s a checklist every multilingual app should implement:
44
+
45
+ - **Set `hreflang` meta tags in `<head>`**
46
+ Helps Google understand which versions exist for each language.
47
+
48
+ - **List all translated pages in `sitemap.xml`**
49
+ Use the `xhtml` schema so crawlers can find alternates easily.
50
+
51
+ - **Exclude private/localized routes in `robots.txt`**
52
+ e.g. don’t let `/dashboard`, `/fr/dashboard`, `/es/dashboard` be indexed.
53
+
54
+ - **Use localized links**
55
+ Example: `<a href="/fr/about">À propos</a>` instead of linking to the default `/about`.
56
+
57
+ These are simple steps — but skipping them can cost you visibility.
58
+
59
+ ---
60
+
61
+ ## Implementation Examples
62
+
63
+ Developers often forget to properly reference their pages across locales so let’s look at how this works in practice with different libraries.
64
+
65
+ ### **next-intl**
66
+
67
+ <Tabs>
68
+ <TabItem label="next-intl">
69
+
70
+ ```tsx fileName="src/app/[locale]/about/layout.tsx
71
+ import type { Metadata } from "next";
72
+ import { locales, defaultLocale } from "@/i18n";
73
+ import { getTranslations, unstable_setRequestLocale } from "next-intl/server";
74
+
75
+ function localizedPath(locale: string, path: string) {
76
+ return locale === defaultLocale ? path : `/${locale}${path}`;
77
+ }
78
+
79
+ export async function generateMetadata({
80
+ params,
81
+ }: {
82
+ params: { locale: string };
83
+ }): Promise<Metadata> {
84
+ const { locale } = params;
85
+ const t = await getTranslations({ locale, namespace: "about" });
86
+
87
+ const url = "/about";
88
+ const languages = Object.fromEntries(
89
+ locales.map((l) => [l, localizedPath(l, url)])
90
+ );
91
+
92
+ return {
93
+ title: t("title"),
94
+ description: t("description"),
95
+ alternates: {
96
+ canonical: localizedPath(locale, url),
97
+ languages: { ...languages, "x-default": url },
98
+ },
99
+ };
100
+ }
101
+
102
+ // ... Rest of the page code
103
+ ```
104
+
105
+ ```tsx fileName="src/app/sitemap.ts"
106
+ import type { MetadataRoute } from "next";
107
+ import { locales, defaultLocale } from "@/i18n";
108
+
109
+ const origin = "https://example.com";
110
+
111
+ const formatterLocalizedPath = (locale: string, path: string) =>
112
+ locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;
113
+
114
+ export default function sitemap(): MetadataRoute.Sitemap {
115
+ const aboutLanguages = Object.fromEntries(
116
+ locales.map((l) => [l, formatterLocalizedPath(l, "/about")])
117
+ );
118
+
119
+ return [
120
+ {
121
+ url: formatterLocalizedPath(defaultLocale, "/about"),
122
+ lastModified: new Date(),
123
+ changeFrequency: "monthly",
124
+ priority: 0.7,
125
+ alternates: { languages: aboutLanguages },
126
+ },
127
+ ];
128
+ }
129
+ ```
130
+
131
+ ```tsx fileName="src/app/robots.ts"
132
+ import type { MetadataRoute } from "next";
133
+ import { locales, defaultLocale } from "@/i18n";
134
+
135
+ const origin = "https://example.com";
136
+ const withAllLocales = (path: string) => [
137
+ path,
138
+ ...locales.filter((l) => l !== defaultLocale).map((l) => `/${l}${path}`),
139
+ ];
140
+
141
+ export default function robots(): MetadataRoute.Robots {
142
+ const disallow = [
143
+ ...withAllLocales("/dashboard"),
144
+ ...withAllLocales("/admin"),
145
+ ];
146
+
147
+ return {
148
+ rules: { userAgent: "*", allow: ["/"], disallow },
149
+ host: origin,
150
+ sitemap: `${origin}/sitemap.xml`,
151
+ };
152
+ }
153
+ ```
154
+
155
+ ### **next-i18next**
156
+
157
+ </TabItem>
158
+ <TabItem label="next-i18next">
159
+
160
+ ```ts fileName="i18n.config.ts"
161
+ export const locales = ["en", "fr"] as const;
162
+ export type Locale = (typeof locales)[number];
163
+ export const defaultLocale: Locale = "en";
164
+
165
+ /** Prefix path with locale unless it's the default locale */
166
+ export function localizedPath(locale: string, path: string) {
167
+ return locale === defaultLocale ? path : `/${locale}${path}`;
168
+ }
169
+
170
+ /** Absolute URL helper */
171
+ const ORIGIN = "https://example.com";
172
+ export function abs(locale: string, path: string) {
173
+ return `${ORIGIN}${localizedPath(locale, path)}`;
174
+ }
175
+ ```
176
+
177
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
178
+ import type { Metadata } from "next";
179
+ import { locales, defaultLocale, localizedPath } from "@/i18n.config";
180
+
181
+ export async function generateMetadata({
182
+ params,
183
+ }: {
184
+ params: { locale: string };
185
+ }): Promise<Metadata> {
186
+ const { locale } = params;
187
+
188
+ // Dynamically import the correct JSON file
189
+ const messages = (await import(`@/../public/locales/${locale}/about.json`))
190
+ .default;
191
+
192
+ const languages = Object.fromEntries(
193
+ locales.map((l) => [l, localizedPath(l, "/about")])
194
+ );
195
+
196
+ return {
197
+ title: messages.title,
198
+ description: messages.description,
199
+ alternates: {
200
+ canonical: localizedPath(locale, "/about"),
201
+ languages: { ...languages, "x-default": "/about" },
202
+ },
203
+ };
204
+ }
205
+
206
+ export default async function AboutPage() {
207
+ return <h1>About</h1>;
208
+ }
209
+ ```
210
+
211
+ ```ts fileName="src/app/sitemap.ts"
212
+ import type { MetadataRoute } from "next";
213
+ import { locales, defaultLocale, abs } from "@/i18n.config";
214
+
215
+ export default function sitemap(): MetadataRoute.Sitemap {
216
+ const languages = Object.fromEntries(
217
+ locales.map((l) => [l, abs(l, "/about")])
218
+ );
219
+ return [
220
+ {
221
+ url: abs(defaultLocale, "/about"),
222
+ lastModified: new Date(),
223
+ changeFrequency: "monthly",
224
+ priority: 0.7,
225
+ alternates: { languages },
226
+ },
227
+ ];
228
+ }
229
+ ```
230
+
231
+ ```ts fileName="src/app/robots.ts"
232
+ import type { MetadataRoute } from "next";
233
+ import { locales, defaultLocale, localizedPath } from "@/i18n.config";
234
+
235
+ const ORIGIN = "https://example.com";
236
+
237
+ const expandAllLocales = (path: string) => [
238
+ localizedPath(defaultLocale, path),
239
+ ...locales
240
+ .filter((l) => l !== defaultLocale)
241
+ .map((l) => localizedPath(l, path)),
242
+ ];
243
+
244
+ export default function robots(): MetadataRoute.Robots {
245
+ const disallow = [
246
+ ...expandAllLocales("/dashboard"),
247
+ ...expandAllLocales("/admin"),
248
+ ];
249
+
250
+ return {
251
+ rules: { userAgent: "*", allow: ["/"], disallow },
252
+ host: ORIGIN,
253
+ sitemap: `${ORIGIN}/sitemap.xml`,
254
+ };
255
+ }
256
+ ```
257
+
258
+ ### **Intlayer**
259
+
260
+ </TabItem>
261
+ <TabItem label="intlayer">
262
+
263
+ ````typescript fileName="src/app/[locale]/about/layout.tsx"
264
+ import { getIntlayer, getMultilingualUrls } from "intlayer";
265
+ import type { Metadata } from "next";
266
+ import type { LocalPromiseParams } from "next-intlayer";
267
+
268
+ export const generateMetadata = async ({
269
+ params,
270
+ }: LocalPromiseParams): Promise<Metadata> => {
271
+ const { locale } = await params;
272
+
273
+ const metadata = getIntlayer("page-metadata", locale);
274
+
275
+ /**
276
+ * Generates an object containing all url for each locale.
277
+ *
278
+ * Example:
279
+ * ```ts
280
+ * getMultilingualUrls('/about');
281
+ *
282
+ * // Returns
283
+ * // {
284
+ * // en: '/about',
285
+ * // fr: '/fr/about',
286
+ * // es: '/es/about',
287
+ * // }
288
+ * ```
289
+ */
290
+ const multilingualUrls = getMultilingualUrls("/about");
291
+
292
+ return {
293
+ ...metadata,
294
+ alternates: {
295
+ canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
296
+ languages: { ...multilingualUrls, "x-default": "/about" },
297
+ },
298
+ };
299
+ };
300
+
301
+ // ... Rest of the page code
302
+ ````
303
+
304
+ ```tsx fileName="src/app/sitemap.ts"
305
+ import { getMultilingualUrls } from "intlayer";
306
+ import type { MetadataRoute } from "next";
307
+
308
+ const sitemap = (): MetadataRoute.Sitemap => [
309
+ {
310
+ url: "https://example.com/about",
311
+ alternates: {
312
+ languages: { ...getMultilingualUrls("https://example.com/about") },
313
+ },
314
+ },
315
+ ];
316
+ ```
317
+
318
+ ```tsx fileName="src/app/robots.ts"
319
+ import { getMultilingualUrls } from "intlayer";
320
+ import type { MetadataRoute } from "next";
321
+
322
+ const getAllMultilingualUrls = (urls: string[]) =>
323
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
324
+
325
+ const robots = (): MetadataRoute.Robots => ({
326
+ rules: {
327
+ userAgent: "*",
328
+ allow: ["/"],
329
+ disallow: getAllMultilingualUrls(["/dashboard"]),
330
+ },
331
+ host: "https://example.com",
332
+ sitemap: `https://example.com/sitemap.xml`,
333
+ });
334
+
335
+ export default robots;
336
+ ```
337
+
338
+ > Intlayer provides a `getMultilingualUrls` function to generate multilingual URLs for your sitemap.
339
+
340
+ </TabItem>
341
+ </Tabs>
342
+
343
+ ---
344
+
345
+ ## Conclusion
346
+
347
+ Getting i18n right in Next.js isn’t just about translating text, it’s about making sure search engines and users know exactly which version of your content to serve.
348
+ Setting up hreflang, sitemaps, and robots rules is what turns translations into real SEO value.
349
+
350
+ While next-intl and next-i18next give you solid ways to wire this up, they usually require a lot of manual setup to keep things consistent across locales.
351
+
352
+ This is where Intlayer really shines:
353
+
354
+ It comes with built-in helpers like getMultilingualUrls, making hreflang, sitemap, and robots integration almost effortless.
355
+
356
+ Metadata stays centralized instead of scattered across JSON files or custom utilities.
357
+
358
+ It’s designed for Next.js from the ground up, so you spend less time debugging config and more time shipping.
359
+
360
+ If your goal is not just to translate but to scale multilingual SEO without friction, Intlayer gives you the cleanest, most future-proof setup.