<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jirka Svoboda</title>
    <description>The latest articles on DEV Community by Jirka Svoboda (@svobik7).</description>
    <link>https://dev.to/svobik7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F496106%2F998c5b44-74c3-4051-a833-9ea71734935b.jpeg</url>
      <title>DEV Community: Jirka Svoboda</title>
      <link>https://dev.to/svobik7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/svobik7"/>
    <language>en</language>
    <item>
      <title>Step-by-step guide for SEO-friendly i18n routes in Next.js 13</title>
      <dc:creator>Jirka Svoboda</dc:creator>
      <pubDate>Wed, 29 Mar 2023 11:54:21 +0000</pubDate>
      <link>https://dev.to/svobik7/step-by-step-guide-for-seo-friendly-i18n-routes-in-nextjs-13-3j0f</link>
      <guid>https://dev.to/svobik7/step-by-step-guide-for-seo-friendly-i18n-routes-in-nextjs-13-3j0f</guid>
      <description>&lt;p&gt;Next.js &lt;a href="https://beta.nextjs.org/docs/guides/internationalization#routing-overview"&gt;official guide&lt;/a&gt; recommends wrapping routes with a dynamic &lt;code&gt;[lang]&lt;/code&gt; segment. Although it works it comes with the drawback of a poor SEO score (see the &lt;a href="https://dev.to/svobik7/dont-use-dynamic-lang-segment-for-your-i18n-nextjs-routes-3k05"&gt;previous post&lt;/a&gt; for more details).&lt;/p&gt;




&lt;h3&gt;
  
  
  TL;DR
&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://github.com/svobik7/next-roots"&gt;next-roots&lt;/a&gt; library for generating translated routes and creating page links in your app.&lt;/p&gt;




&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;This guide requires Next.js 13 project to be installed with app dir support enabled. Follow the official installation guide if needed.&lt;/p&gt;




&lt;h3&gt;
  
  
  Objectives
&lt;/h3&gt;

&lt;p&gt;Build an SEO-friendly internationalized blog site that meets the following acceptance criteria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AC1&lt;/strong&gt;: The site offers English, Spain, and Czech localization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AC2&lt;/strong&gt;: The site meets demanded URL structure (see below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AC3&lt;/strong&gt;: The login and signup share the same layout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Demanded URL structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Home page: &lt;code&gt;/en&lt;/code&gt;, &lt;code&gt;/es&lt;/code&gt;, &lt;code&gt;/cs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Article detail: &lt;code&gt;/en/[id]&lt;/code&gt;, &lt;code&gt;/es/[id]&lt;/code&gt;, &lt;code&gt;/cs/[id]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Login page: &lt;code&gt;/en/login&lt;/code&gt;, &lt;code&gt;/es/acceso&lt;/code&gt;, &lt;code&gt;/cs/prihlaseni&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Signup page: &lt;code&gt;/en/signup&lt;/code&gt;, &lt;code&gt;/es/registrar&lt;/code&gt;, &lt;code&gt;/cs/registrace&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Installing &lt;a href="https://github.com/svobik7/next-roots"&gt;next-roots&lt;/a&gt; package
&lt;/h3&gt;

&lt;p&gt;To be able to generate translated routes we need to install additional package called next-roots.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;yarn add next-roots&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is also required to add ESBuild into devDependencies to be able to compile i18n configuration files for next-roots&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;yarn add --dev exbuild&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Setting up the origin
&lt;/h3&gt;

&lt;p&gt;To be able to generate localised routes the original (default locale) structure must be set as first. Let's use English as the default locale in our case and create the following routes manually under the folder called &lt;code&gt;roots&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── roots
│   ├── &lt;span class="o"&gt;(&lt;/span&gt;auth&lt;span class="o"&gt;)&lt;/span&gt;
│   │   ├── layout.tsx
│   │   ├── login
│   │   │   └── page.ts
│   │   └── signup
│   │       └── page.ts
│   ├── &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
│   │   └── page.ts
│   └── page.js
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the structure is the same as you would use for Next.js APP folder. Instead of &lt;code&gt;roots&lt;/code&gt; folder you can use anything you want and specify that name in &lt;code&gt;roots.config.js&lt;/code&gt; file as &lt;code&gt;originDir&lt;/code&gt; param.&lt;/p&gt;




&lt;h3&gt;
  
  
  Setting up &lt;code&gt;roots.config.js&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;Simple configuration file called &lt;code&gt;roots.config.js&lt;/code&gt; is required to be placed in your project root to tell next-roots where to look for the origin files, which locales are we going to generate and where to save localised files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// roots.config.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// where original routes are placed&lt;/span&gt;
  &lt;span class="na"&gt;originDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;roots&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// where translated routes will be saved&lt;/span&gt;
  &lt;span class="na"&gt;localizedDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="c1"&gt;// which locales are we going to use (URL prefixes)&lt;/span&gt;
  &lt;span class="na"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// which locale is considered as default when no other match&lt;/span&gt;
  &lt;span class="na"&gt;defaultLocale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// serves default locale on "/en" instead of "/"&lt;/span&gt;
  &lt;span class="na"&gt;prefixDefaultLocale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Setting up URL translations
&lt;/h3&gt;

&lt;p&gt;For every route segment that needs to be translated the i18n file must be created right next to its page file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── roots
│   ├── &lt;span class="o"&gt;(&lt;/span&gt;auth&lt;span class="o"&gt;)&lt;/span&gt;
│   │   ├── layout.tsx
│   │   ├── login
│   │   │   ├── i18n.js
│   │   │   └── page.ts
│   │   └── signup
│   │       ├── i18n.js
│   │       └── page.ts
│   ├── &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
│   │   └── page.ts
│   └── page.js
└── ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;i18n&lt;/code&gt; file can be written in JS or TS. That is because every i18n file is compiled using ESBuild under the hood.&lt;/p&gt;

&lt;p&gt;Let's define our translations for the &lt;code&gt;login&lt;/code&gt; segment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in roots/(auth)/login/i18n.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routeNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;acesso&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prihlaseni&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// you don't need to specify "en" translation as long as it matches the route folder name&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to fetch the translation from async storage you can use async function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in roots/(auth)/login/i18n.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateRouteNames&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// "getTranslation" is custom async function that loads translated paths from DB&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;esPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;csPath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getTranslations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;esPath&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;csPath&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same must be done for &lt;code&gt;signup&lt;/code&gt; segment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in roots/(auth)/signup/i18n.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routeNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;registrar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;registrace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Generating translated routes
&lt;/h3&gt;

&lt;p&gt;When you are done with configuration and i18n files it is time to run the generator.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yarn next-roots&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That will create the APP folder shaped like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── app
│   ├── en
│   │   ├── &lt;span class="o"&gt;(&lt;/span&gt;auth&lt;span class="o"&gt;)&lt;/span&gt;
│   │   │   ├── layout.tsx
│   │   │   ├── login
│   │   │   │   └── page.ts
│   │   │   └── signup
│   │   │       └── page.ts
│   │   ├── &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
│   │   │   └── page.ts
│   │   └── page.js
│   ├── es
│   │   ├── &lt;span class="o"&gt;(&lt;/span&gt;auth&lt;span class="o"&gt;)&lt;/span&gt;
│   │   │   ├── layout.tsx
│   │   │   ├── registrar
│   │   │   │   └── page.ts
│   │   │   └── acceso
│   │   │       ├── i18n.js
│   │   │       └── page.ts
│   │   ├── &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
│   │   │   └── page.ts
│   │   └── page.js
│   ├── cs
│   │   ├── &lt;span class="o"&gt;(&lt;/span&gt;auth&lt;span class="o"&gt;)&lt;/span&gt;
│   │   │   ├── layout.tsx
│   │   │   ├── prihlaseni
│   │   │   │   └── page.ts
│   │   │   └── registrace
│   │   │       └── page.ts
│   │   ├── &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
│   │   │   └── page.ts
│   │   └── page.js
├── roots
│   ├── &lt;span class="o"&gt;(&lt;/span&gt;auth&lt;span class="o"&gt;)&lt;/span&gt;
│   │   ├── layout.tsx
│   │   ├── login
│   │   │   ├── i18n.js
│   │   │   └── page.ts
│   │   └── signup
│   │       ├── i18n.js
│   │       └── page.ts
│   ├── &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
│   │   └── page.ts
│   └── page.js
└── ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;i18n&lt;/code&gt; files are not copied into APP folder. Right now when you run &lt;code&gt;yarn next dev&lt;/code&gt; you will be able to access your pages on different locales without any rewrites or dynamic &lt;code&gt;[lang]&lt;/code&gt; segment. All required routes are now statically placed in your APP folder.&lt;/p&gt;

&lt;p&gt;Components like pages, layouts or template that were generated in app folder just re-exports what was exported from corresponding components in &lt;code&gt;roots&lt;/code&gt; folder. That means your &lt;code&gt;roots&lt;/code&gt; are still bundled into your application.&lt;/p&gt;

&lt;p&gt;Bare in mind that once you need to change the translation of your need to run &lt;code&gt;next-roots&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Read more in &lt;a href="https://github.com/svobik7/next-roots"&gt;next-roots&lt;/a&gt; readme and its example.&lt;/p&gt;




&lt;h3&gt;
  
  
  BONUS: Creating links
&lt;/h3&gt;

&lt;p&gt;NextRoots comes with its own strongly typed Router using which you can easily create localised links. It warns you when you forget about passing down the dynamic parameters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RouteName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-roots&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// for getting '/cs/prihlaseni'&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// for getting '/es/acesso'&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// typescript will yield at you here as /invalid is not a valid route&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/invalid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routeNameValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routeNameInvalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/invalid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// yields TS error&lt;/span&gt;


&lt;span class="c1"&gt;// for getting '/cs/1'&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/[id]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;articleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// for getting '/es/1'&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/[id]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;articleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// typescript will yield at you here because of the missing required parameter called “id”&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getHref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/[id]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routeDynamic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/[id]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paramsDynamicValid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteParamsDynamic&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;routeDynamic&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// typescript will yield at you here because of the missing required parameter called “id”&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paramsDynamicInvalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteParamsDynamic&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;routeDynamic&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that thanks to typed router your IDE autocompletion will help you to fill the route name in &lt;code&gt;router.getHref()&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;WHile &lt;code&gt;[lang]&lt;/code&gt; approach works well in the most cases when it comes to SEO and easy links the &lt;a href="https://github.com/svobik7/next-roots"&gt;next-roots&lt;/a&gt; package can be pretty handy.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Don't use dynamic [lang] segment for your i18n Next.js routes</title>
      <dc:creator>Jirka Svoboda</dc:creator>
      <pubDate>Wed, 29 Mar 2023 11:53:25 +0000</pubDate>
      <link>https://dev.to/svobik7/dont-use-dynamic-lang-segment-for-your-i18n-nextjs-routes-3k05</link>
      <guid>https://dev.to/svobik7/dont-use-dynamic-lang-segment-for-your-i18n-nextjs-routes-3k05</guid>
      <description>&lt;p&gt;First of all, check the &lt;a href="https://beta.nextjs.org/docs/guides/internationalization#routing-overview" rel="noopener noreferrer"&gt;official guide&lt;/a&gt; explaining how to handle internationalized routing in Next.js 13 if you are not familiar with the dynamic &lt;code&gt;[lang]&lt;/code&gt; segment.&lt;/p&gt;




&lt;h3&gt;
  
  
  TL;DR
&lt;/h3&gt;

&lt;p&gt;Avoid dynamic &lt;code&gt;[lang]&lt;/code&gt; segment because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It makes URL translations and page links clunky. &lt;/li&gt;
&lt;li&gt;It keeps your runtime busy checking tons of rewrites.&lt;/li&gt;
&lt;li&gt;It creates content duplication by making your pages available on multiple URLs.&lt;/li&gt;
&lt;li&gt;It knocks your SEO score down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;a href="https://github.com/svobik7/next-roots" rel="noopener noreferrer"&gt;next-roots&lt;/a&gt;, the ultimate library for i18n file-based routing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It generates translated file routes within explicitly stated locales. &lt;/li&gt;
&lt;li&gt;It provides a type-safe API for creating page links&lt;/li&gt;
&lt;li&gt;It needs no rewrites&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The dynamic &lt;code&gt;[lang]&lt;/code&gt; segment sucks!
&lt;/h3&gt;

&lt;p&gt;Don't get me wrong it works well in some cases but when you need to take care of your SEO score, it sucks. While the content translation is a breeze the URL translations become clunky.&lt;/p&gt;

&lt;p&gt;Config option called &lt;a href="https://github.com/vercel/next.js/blob/canary/examples/rewrites/next.config.js" rel="noopener noreferrer"&gt;rewrites&lt;/a&gt; can be used to set all possible URL translations and point them to proper destinations. Unfortunately, that goes against the benefits of file-based routing. All those rewrites must be checked before every request finds its target. In runtime. It is true even for the URLs that can be routed directly with no rewrites.&lt;/p&gt;

&lt;p&gt;Not to mention the hard time of managing those rewrites for a site with 30+ routes and 5 localizations. And creating page links? Uff, even more frustrating. And yet the most annoying drawback is that your site ends up with one page accessible on multiple URLs. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7h4uwiza4yo5s8xai4bj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7h4uwiza4yo5s8xai4bj.png" alt="Content duplication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is because dynamic &lt;code&gt;[lang]&lt;/code&gt; segment does not care about translated URL paths by default. Even you can set rewrites rules you still end up with having one page available on two URLs.&lt;/p&gt;

&lt;p&gt;Such content duplication simply knocks your SEO score down with no mercy. Isn't there a way to overcome all those pitfall? Well, there is. By utilizing static locales.&lt;/p&gt;




&lt;h3&gt;
  
  
  Static locales segments rock!
&lt;/h3&gt;

&lt;p&gt;Drop that dynamic &lt;code&gt;[lang]&lt;/code&gt; parent segment and spread all your translated file routes among explicitly stated locales.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj4kpyaxd93207p3h1tq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj4kpyaxd93207p3h1tq.png" alt="Static locales"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That keeps your runtime free of looping through the rewrites. No page is available on multiple URLs. And your SEO score is not damaged. &lt;/p&gt;

&lt;p&gt;However, managing those translations stays cumbersome and page links are far from being easy to create. For those to become convenient you need a generator. And a router.&lt;/p&gt;




&lt;h3&gt;
  
  
  Meet next-roots
&lt;/h3&gt;

&lt;p&gt;The ultimate library for handling file-based i18n routing in your app. It treats your file routes as they were meant to be - just as routes with no business logic. It generates all translated file routes based on the configuration schema. And it provides a type-safe API for creating page links. Neat!&lt;/p&gt;

&lt;p&gt;Find more in &lt;a href="https://github.com/svobik7/next-roots" rel="noopener noreferrer"&gt;next-roots&lt;/a&gt; repo or in &lt;a href="https://dev.to/svobik7/step-by-step-guide-for-seo-friendly-i18n-routes-in-nextjs-13-3j0f"&gt;Step-by-step guide for SEO friendly i18n routes in Next.js 13&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
