@intlayer/docs 6.1.4 → 6.1.5

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 (35) hide show
  1. package/blog/ar/next-i18next_vs_next-intl_vs_intlayer.md +1135 -75
  2. package/blog/ar/nextjs-multilingual-seo-comparison.md +364 -0
  3. package/blog/de/next-i18next_vs_next-intl_vs_intlayer.md +1139 -72
  4. package/blog/de/nextjs-multilingual-seo-comparison.md +362 -0
  5. package/blog/en/next-i18next_vs_next-intl_vs_intlayer.md +224 -240
  6. package/blog/en/nextjs-multilingual-seo-comparison.md +360 -0
  7. package/blog/en-GB/next-i18next_vs_next-intl_vs_intlayer.md +1134 -37
  8. package/blog/en-GB/nextjs-multilingual-seo-comparison.md +360 -0
  9. package/blog/es/next-i18next_vs_next-intl_vs_intlayer.md +1122 -64
  10. package/blog/es/nextjs-multilingual-seo-comparison.md +363 -0
  11. package/blog/fr/next-i18next_vs_next-intl_vs_intlayer.md +1132 -75
  12. package/blog/fr/nextjs-multilingual-seo-comparison.md +362 -0
  13. package/blog/hi/nextjs-multilingual-seo-comparison.md +363 -0
  14. package/blog/it/next-i18next_vs_next-intl_vs_intlayer.md +1120 -55
  15. package/blog/it/nextjs-multilingual-seo-comparison.md +363 -0
  16. package/blog/ja/next-i18next_vs_next-intl_vs_intlayer.md +1140 -76
  17. package/blog/ja/nextjs-multilingual-seo-comparison.md +362 -0
  18. package/blog/ko/next-i18next_vs_next-intl_vs_intlayer.md +1129 -73
  19. package/blog/ko/nextjs-multilingual-seo-comparison.md +362 -0
  20. package/blog/pt/next-i18next_vs_next-intl_vs_intlayer.md +1133 -76
  21. package/blog/pt/nextjs-multilingual-seo-comparison.md +362 -0
  22. package/blog/ru/next-i18next_vs_next-intl_vs_intlayer.md +1142 -74
  23. package/blog/ru/nextjs-multilingual-seo-comparison.md +370 -0
  24. package/blog/tr/nextjs-multilingual-seo-comparison.md +362 -0
  25. package/blog/zh/next-i18next_vs_next-intl_vs_intlayer.md +1142 -75
  26. package/blog/zh/nextjs-multilingual-seo-comparison.md +394 -0
  27. package/dist/cjs/generated/blog.entry.cjs +16 -0
  28. package/dist/cjs/generated/blog.entry.cjs.map +1 -1
  29. package/dist/esm/generated/blog.entry.mjs +16 -0
  30. package/dist/esm/generated/blog.entry.mjs.map +1 -1
  31. package/dist/types/generated/blog.entry.d.ts +1 -0
  32. package/dist/types/generated/blog.entry.d.ts.map +1 -1
  33. package/docs/en/interest_of_intlayer.md +2 -2
  34. package/package.json +10 -10
  35. package/src/generated/blog.entry.ts +16 -0
@@ -0,0 +1,370 @@
1
+ ---
2
+ createdAt: 2025-09-28
3
+ updatedAt: 2025-09-28
4
+ title: SEO и i18n в Next.js
5
+ description: Узнайте, как настроить многоязычный SEO в вашем приложении Next.js с использованием next-intl, next-i18next и Intlayer.
6
+ keywords:
7
+ - Intlayer
8
+ - SEO
9
+ - Интернационализация
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 и i18n в Next.js: Перевода недостаточно
22
+
23
+ Когда разработчики думают об интернационализации (i18n), их первой реакцией часто является: _перевести контент_. Но обычно забывают, что главная цель интернационализации — сделать ваш сайт более заметным для всего мира.
24
+ Если ваше многоязычное приложение Next.js не сообщает поисковым системам, как сканировать и понимать различные языковые версии, большая часть ваших усилий может остаться незамеченной.
25
+
26
+ В этом блоге мы рассмотрим, **почему i18n — это суперсила SEO**, и как правильно реализовать её в Next.js с помощью `next-intl`, `next-i18next` и `Intlayer`.
27
+
28
+ ---
29
+
30
+ ## Почему SEO и i18n
31
+
32
+ Добавление языков — это не только про удобство пользователя (UX). Это также мощный рычаг для **органической видимости**. Вот почему:
33
+
34
+ 1. **Лучшая обнаруживаемость:** Поисковые системы индексируют локализованные версии и ранжируют их для пользователей, ищущих на своем родном языке.
35
+ 2. **Избежание дублированного контента:** Правильные канонические и альтернативные теги сообщают поисковым роботам, какая страница относится к какому языку.
36
+ 3. **Лучший UX:** Посетители сразу попадают на правильную версию вашего сайта.
37
+ 4. **Конкурентное преимущество:** Немногие сайты хорошо реализуют многоязычное SEO, что даёт вам возможность выделиться.
38
+
39
+ ---
40
+
41
+ ## Лучшие практики многоязычного SEO в Next.js
42
+
43
+ Вот чеклист, который должно реализовать каждое многоязычное приложение:
44
+
45
+ - **Устанавливайте метатеги `hreflang` в `<head>`**
46
+ Помогает Google понять, какие версии существуют для каждого языка.
47
+
48
+ - **Включайте все переведённые страницы в `sitemap.xml`**
49
+ Используйте схему `xhtml`, чтобы поисковые роботы могли легко находить альтернативные версии.
50
+
51
+ - **Исключайте приватные/локализованные маршруты в `robots.txt`**
52
+ Например, не позволяйте индексировать `/dashboard`, `/fr/dashboard`, `/es/dashboard`.
53
+
54
+ - **Используйте локализованные ссылки**
55
+ Пример: `<a href="/fr/about">À propos</a>` вместо ссылки на стандартную `/about`.
56
+
57
+ Это простые шаги — но их пропуск может стоить вам видимости.
58
+
59
+ ---
60
+
61
+ ## Примеры реализации
62
+
63
+ Разработчики часто забывают правильно ссылаться на свои страницы в разных локалях, поэтому давайте посмотрим, как это работает на практике с различными библиотеками.
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
+ // Функция для получения локализованного пути
76
+ function localizedPath(locale: string, path: string) {
77
+ return locale === defaultLocale ? path : `/${locale}${path}`;
78
+ }
79
+
80
+ export async function generateMetadata({
81
+ params,
82
+ }: {
83
+ params: { locale: string };
84
+ }): Promise<Metadata> {
85
+ const { locale } = params;
86
+ // Получаем переводы для текущей локали и namespace "about"
87
+ const t = await getTranslations({ locale, namespace: "about" });
88
+
89
+ const url = "/about";
90
+ const languages = Object.fromEntries(
91
+ locales.map((l) => [l, localizedPath(l, url)])
92
+ );
93
+
94
+ return {
95
+ title: t("title"),
96
+ description: t("description"),
97
+ alternates: {
98
+ canonical: localizedPath(locale, url),
99
+ languages: { ...languages, "x-default": url },
100
+ },
101
+ };
102
+ }
103
+
104
+ // ... Остальная часть кода страницы
105
+ ```
106
+
107
+ ```tsx fileName="src/app/sitemap.ts"
108
+ import type { MetadataRoute } from "next";
109
+ import { locales, defaultLocale } from "@/i18n";
110
+
111
+ const origin = "https://example.com";
112
+
113
+ const formatterLocalizedPath = (locale: string, path: string) =>
114
+ locale === defaultLocale ? `${origin}${path}` : `${origin}/${locale}${path}`;
115
+
116
+ export default function sitemap(): MetadataRoute.Sitemap {
117
+ const aboutLanguages = Object.fromEntries(
118
+ locales.map((l) => [l, formatterLocalizedPath(l, "/about")])
119
+ );
120
+
121
+ return [
122
+ {
123
+ url: formatterLocalizedPath(defaultLocale, "/about"),
124
+ lastModified: new Date(),
125
+ changeFrequency: "monthly",
126
+ priority: 0.7,
127
+ alternates: { languages: aboutLanguages },
128
+ },
129
+ ];
130
+ }
131
+ ```
132
+
133
+ ```tsx fileName="src/app/robots.ts"
134
+ import type { MetadataRoute } from "next";
135
+ import { locales, defaultLocale } from "@/i18n";
136
+
137
+ const origin = "https://example.com";
138
+ const withAllLocales = (path: string) => [
139
+ path,
140
+ ...locales.filter((l) => l !== defaultLocale).map((l) => `/${l}${path}`),
141
+ ];
142
+
143
+ export default function robots(): MetadataRoute.Robots {
144
+ const disallow = [
145
+ ...withAllLocales("/dashboard"),
146
+ ...withAllLocales("/admin"),
147
+ ];
148
+
149
+ return {
150
+ // Правила для роботов
151
+ rules: { userAgent: "*", allow: ["/"], disallow },
152
+ host: origin,
153
+ sitemap: `${origin}/sitemap.xml`,
154
+ };
155
+ }
156
+ rules: { userAgent: "*", allow: ["/"], disallow },
157
+ host: origin,
158
+ sitemap: `${origin}/sitemap.xml`,
159
+ };
160
+ }
161
+ ```
162
+
163
+ ### **next-i18next**
164
+
165
+ </TabItem>
166
+ <TabItem label="next-i18next">
167
+
168
+ ```ts fileName="i18n.config.ts"
169
+ export const locales = ["en", "fr"] as const;
170
+ export type Locale = (typeof locales)[number];
171
+ export const defaultLocale: Locale = "en";
172
+
173
+ /** Добавляет префикс локали к пути, если это не локаль по умолчанию */
174
+ export function localizedPath(locale: string, path: string) {
175
+ return locale === defaultLocale ? path : `/${locale}${path}`;
176
+ }
177
+
178
+ /** Помощник для абсолютного URL */
179
+ const ORIGIN = "https://example.com";
180
+ export function abs(locale: string, path: string) {
181
+ return `${ORIGIN}${localizedPath(locale, path)}`;
182
+ }
183
+ ```
184
+
185
+ ```tsx fileName="src/app/[locale]/about/layout.tsx"
186
+ import type { Metadata } from "next";
187
+ import { locales, defaultLocale, localizedPath } from "@/i18n.config";
188
+
189
+ export async function generateMetadata({
190
+ params,
191
+ }: {
192
+ params: { locale: string };
193
+ }): Promise<Metadata> {
194
+ const { locale } = params;
195
+
196
+ // Динамически импортировать правильный JSON-файл
197
+ const messages = (await import(`@/../public/locales/${locale}/about.json`))
198
+ .default;
199
+
200
+ const languages = Object.fromEntries(
201
+ locales.map((l) => [l, localizedPath(l, "/about")])
202
+ );
203
+
204
+ return {
205
+ title: messages.title,
206
+ description: messages.description,
207
+ alternates: {
208
+ canonical: localizedPath(locale, "/about"),
209
+ languages: { ...languages, "x-default": "/about" },
210
+ },
211
+ };
212
+ }
213
+
214
+ export default async function AboutPage() {
215
+ return <h1>О нас</h1>;
216
+ }
217
+ ```
218
+
219
+ ```ts fileName="src/app/sitemap.ts"
220
+ import type { MetadataRoute } from "next";
221
+ import { locales, defaultLocale, abs } from "@/i18n.config";
222
+
223
+ export default function sitemap(): MetadataRoute.Sitemap {
224
+ const languages = Object.fromEntries(
225
+ locales.map((l) => [l, abs(l, "/about")])
226
+ );
227
+ return [
228
+ {
229
+ url: abs(defaultLocale, "/about"),
230
+ lastModified: new Date(),
231
+ changeFrequency: "monthly",
232
+ priority: 0.7,
233
+ alternates: { languages },
234
+ },
235
+ ];
236
+ }
237
+ ```
238
+
239
+ ```ts fileName="src/app/robots.ts"
240
+ import type { MetadataRoute } from "next";
241
+ import { locales, defaultLocale, localizedPath } from "@/i18n.config";
242
+
243
+ const ORIGIN = "https://example.com";
244
+
245
+ const expandAllLocales = (path: string) => [
246
+ localizedPath(defaultLocale, path),
247
+ ...locales
248
+ .filter((l) => l !== defaultLocale)
249
+ .map((l) => localizedPath(l, path)),
250
+ ];
251
+
252
+ export default function robots(): MetadataRoute.Robots {
253
+ const disallow = [
254
+ ...expandAllLocales("/dashboard"),
255
+ ...expandAllLocales("/admin"),
256
+ ];
257
+
258
+ return {
259
+ rules: { userAgent: "*", allow: ["/"], disallow },
260
+ host: ORIGIN,
261
+ sitemap: `${ORIGIN}/sitemap.xml`,
262
+ };
263
+ }
264
+ ```
265
+
266
+ ### **Intlayer**
267
+
268
+ </TabItem>
269
+ <TabItem label="intlayer">
270
+
271
+ ````typescript fileName="src/app/[locale]/about/layout.tsx"
272
+ import { getIntlayer, getMultilingualUrls } from "intlayer";
273
+ import type { Metadata } from "next";
274
+ import type { LocalPromiseParams } from "next-intlayer";
275
+
276
+ export const generateMetadata = async ({
277
+ params,
278
+ }: LocalPromiseParams): Promise<Metadata> => {
279
+ const { locale } = await params;
280
+
281
+ const metadata = getIntlayer("page-metadata", locale);
282
+
283
+ /**
284
+ * Генерирует объект, содержащий все URL для каждого языка.
285
+ *
286
+ * Пример:
287
+ * ```ts
288
+ * getMultilingualUrls('/about');
289
+ *
290
+ * // Возвращает
291
+ * // {
292
+ * // en: '/about',
293
+ * // fr: '/fr/about',
294
+ * // es: '/es/about',
295
+ * // }
296
+ * ```
297
+ */
298
+ const multilingualUrls = getMultilingualUrls("/about");
299
+
300
+ return {
301
+ ...metadata,
302
+ alternates: {
303
+ canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
304
+ languages: { ...multilingualUrls, "x-default": "/about" },
305
+ },
306
+ };
307
+ };
308
+
309
+ // ... Остальной код страницы
310
+ ````
311
+
312
+ ```tsx fileName="src/app/sitemap.ts"
313
+ import { getMultilingualUrls } from "intlayer";
314
+ import type { MetadataRoute } from "next";
315
+
316
+ const sitemap = (): MetadataRoute.Sitemap => [
317
+ {
318
+ url: "https://example.com/about",
319
+ alternates: {
320
+ languages: { ...getMultilingualUrls("https://example.com/about") },
321
+ },
322
+ },
323
+ ];
324
+ ```
325
+
326
+ ```tsx fileName="src/app/robots.ts"
327
+ import { getMultilingualUrls } from "intlayer";
328
+ import type { MetadataRoute } from "next";
329
+
330
+ // Функция для получения всех многоязычных URL из массива URL
331
+ const getAllMultilingualUrls = (urls: string[]) =>
332
+ urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
333
+
334
+ // Конфигурация robots.txt с правилами для поисковых роботов
335
+ const robots = (): MetadataRoute.Robots => ({
336
+ rules: {
337
+ userAgent: "*", // Правила применяются ко всем роботам
338
+ allow: ["/"], // Разрешенные пути
339
+ disallow: getAllMultilingualUrls(["/dashboard"]), // Запрещенные пути (все языковые версии /dashboard)
340
+ },
341
+ host: "https://example.com", // Хост сайта
342
+ sitemap: `https://example.com/sitemap.xml`, // Путь к карте сайта
343
+ });
344
+
345
+ export default robots;
346
+ ```
347
+
348
+ > Intlayer предоставляет функцию `getMultilingualUrls` для генерации многоязычных URL-адресов для вашей карты сайта.
349
+
350
+ </TabItem>
351
+ </Tabs>
352
+
353
+ ---
354
+
355
+ ## Заключение
356
+
357
+ Правильная реализация i18n в Next.js — это не просто перевод текста, а обеспечение того, чтобы поисковые системы и пользователи точно знали, какую версию вашего контента показывать.
358
+ Настройка hreflang, карт сайта и правил для robots — это то, что превращает переводы в реальную SEO-ценность.
359
+
360
+ Хотя next-intl и next-i18next предоставляют надежные способы для этого, они обычно требуют много ручной настройки, чтобы поддерживать согласованность между локалями.
361
+
362
+ Именно здесь Intlayer действительно выделяется:
363
+
364
+ Он поставляется с встроенными помощниками, такими как getMultilingualUrls, что делает интеграцию hreflang, карты сайта и robots практически без усилий.
365
+
366
+ Метаданные остаются централизованными, а не разбросанными по JSON-файлам или пользовательским утилитам.
367
+
368
+ Он разработан специально для Next.js с нуля, поэтому вы тратите меньше времени на отладку конфигурации и больше времени на выпуск продукта.
369
+
370
+ Если ваша цель — не просто переводить, а масштабировать многоязычное SEO без лишних сложностей, Intlayer предоставляет вам самое чистое и перспективное решение.