I am currently working on a website using Next.js 14, with the aim of exporting it as a static site for distribution via a CDN (Cloudflare Pages). My site requires internationalization (i18n) support for multiple languages. I have set up a folder structure for language support, which looks like this:
- [language]
-- layout.tsx // generateStaticParams with possible languages
-- page.tsx
-- ...
This setup allows me to access pages with language prefixes in the URL, such as /en/example
and /de/example
.
However, I want to implement a default language (e.g., English) that is accessible at the root path without a language prefix (/example
). Importantly, I do not wish to redirect users to a URL with the language prefix for SEO purposes. Nor can I use the rewrite function because I'm using static export.
Here are my specific requirements:
- Access the default language pages directly via the root path (e.g.,
/example
for English). - Avoid redirects to language-prefixed URLs (e.g., not redirecting
/example
to/en/example
). - Maintain the ability to access other languages with their respective prefixes (e.g.,
/de/example
for German).
I am looking for guidance on:
How to realise this with Next.js 14 to serve the default language pages at the root path without a language prefix. Ensuring that this setup is compatible with the static export feature of Next.js.
Any insights, code snippets, or references to relevant documentation would be greatly appreciated.
I am currently working on a website using Next.js 14, with the aim of exporting it as a static site for distribution via a CDN (Cloudflare Pages). My site requires internationalization (i18n) support for multiple languages. I have set up a folder structure for language support, which looks like this:
- [language]
-- layout.tsx // generateStaticParams with possible languages
-- page.tsx
-- ...
This setup allows me to access pages with language prefixes in the URL, such as /en/example
and /de/example
.
However, I want to implement a default language (e.g., English) that is accessible at the root path without a language prefix (/example
). Importantly, I do not wish to redirect users to a URL with the language prefix for SEO purposes. Nor can I use the rewrite function because I'm using static export.
Here are my specific requirements:
- Access the default language pages directly via the root path (e.g.,
/example
for English). - Avoid redirects to language-prefixed URLs (e.g., not redirecting
/example
to/en/example
). - Maintain the ability to access other languages with their respective prefixes (e.g.,
/de/example
for German).
I am looking for guidance on:
How to realise this with Next.js 14 to serve the default language pages at the root path without a language prefix. Ensuring that this setup is compatible with the static export feature of Next.js.
Any insights, code snippets, or references to relevant documentation would be greatly appreciated.
Share Improve this question asked Jan 15, 2024 at 18:30 Lars FliegerLars Flieger 2,5541 gold badge23 silver badges57 bronze badges 4 |8 Answers
Reset to default 3If I understand your question, Nextjs is a file routing system every page.js/ts
or route.js/ts
files are standing for a page in the web app. So, if you going with the structure you provided in you question, you will need to structure your folders and files this way and to avoid duplicate in your code.
components
- HomePage.ts // Shared component for languages
- AboutPage.ts
App
- layout.tsx
- page.ts // for default language pages
- about
-- page.ts
- contactus
-- page.ts
- [language]
-- layout.tsx
-- page.ts // for other languages pages
-- about
--- page.ts
-- contactus
--- page.ts
The other approach you can follow is to manage the language by state managements like Redux
or Zustand
and translate/fetch the data the current language in the state, this way you do not need to create about
pages files.
- layout.tsx
- page.ts // for all language pages
- about
-- page.ts
- contactus
-- page.ts
additionally you can configure you next.config.js this way:
module.exports = {
i18n: {
locales: ['en', 'de'], // Add your default language and other languages
defaultLocale: 'en', // Set the default language
},
};
You obviously can't use middleware (because it's a static site and you need output: "export"
), so you have to play around with the Next.js router and its folder structure. This means:
/app/[locale]/example/page.tsx
for/de/example
/app/example/page.tsx
for/example
In /app/[locale]/example/page.tsx
you put your page as usual:
export default function Page({ params: { locale } }) {
const t = getTranslations(locale || "en");
return (
<div>
{t.content}
</div>
);
}
…and in /app/example/page.tsx
you just import the page from the other file:
export { default as default } from "../[locale]/example/page";
I've made a post that explains this and more issues relating to static Next.js sites with output: "export"
. I've also made a repo where you can check a working setup of this.
I think that there is a more elegant way how to default to / instead of /en for default locale and it is included into the next-intl documentation already, although it is not easy to find it: https://next-intl-docs.vercel.app/docs/routing#locale-prefix
Don't use a locale prefix for the default locale
If you only want to include a locale prefix for non-default locales, you can configure your routing accordingly.
// config.ts import {LocalePrefix} from 'next-intl/routing'; export const localePrefix = 'as-needed' satisfies LocalePrefix; // ...
In this case, requests where the locale prefix matches the default locale will be redirected (e.g.
/en/about
to/about
). This will affect both prefix-based as well as domain-based routing.Note that:
If you use this strategy, you should make sure that your middleware matcher detects unprefixed pathnames.
If you use the
Link
component, the initial render will point to the prefixed version but will be patched immediately on the client once the component detects that the default locale has rendered. The prefixed version is still valid, but SEO tools might report a hint that the link points to a redirect.
Here is my middleware config for the languages i use
import createMiddleware from "next-intl/middleware";
export default createMiddleware({
// A list of all locales that are supported
locales: ["en", "es", "es-419", "it", "pt", "tr"],
// Used when no locale matches
defaultLocale: "en",
localePrefix: "as-needed",
});
export const config = {
// Match only internationalized pathnames
matcher: [
// Match all pathnames except for
// - … if they start with `/api`, `/_next` or `/_vercel`
// - … the ones containing a dot (e.g. `favicon.ico`)
"/((?!api|_next|_vercel|.*\\..*).*)",
],
};
I just solved this problem. My static website is deployed on cloudflare pages. The core idea is:
Remove localePrefix: 'as-needed' and continue to use always
Configure redirection in cloudflare pages:
# public/_redirects
/blog/* /en/blog/:splat 200
/about /en/about 200
/ /en 200
- (Optional) Configure redirection in nextjs for local debugging convenience
// next.config.mjs
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
async rewrites() {
if (process.env.NODE_ENV === 'development') {
return [
{
source: '/',
destination: '/en',
},
{
source: '/about',
destination: '/en/about',
},
{
source: '/blog',
destination: '/en/blog',
},
{
source: '/blog/:path*',
destination: '/en/blog/:path*',
},
// add others here
];
}
return [];
}
};
export default withNextIntl(nextConfig);
Hope it helps you
I have used a seprate wrapper for using Link conditionally here is my Link component
import { Link as I18Link } from "@/i18n/routing";
import { useLocale } from "next-intl";
import Link from "next/link";
import React, { HTMLAttributes } from "react";
interface MyLinkProps extends HTMLAttributes<HTMLAnchorElement> {
href: string;
children?: React.ReactNode;
}
const MyLink: React.FC<MyLinkProps> = ({ href, children, ...props }) => {
const loca = useLocale();
if (loca === "en") {
return (
<Link href={href} {...props}>
{children}
</Link>
);
} else {
return (
<I18Link href={href} {...props}>
Link
</I18Link>
);
}
};
export default MyLink;
and i also have a changeLanguage component in my header that also do this with useRouter
"use client";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { usePathname, useRouter as useRouterI18 } from "@/i18n/routing";
import { Locale } from "@/types/Types";
import { Globe } from "lucide-react";
import { Button } from "./ui/button";
import { useTranslations } from "next-intl";
import { useRouter as useRouterNext } from "next/navigation";
export const LangSelect = () => {
const t = useTranslations();
const routerNext = useRouterNext();
const routerI18 = useRouterI18();
const pathname = usePathname();
const changeLanguage = ({ locale }: Locale) => {
if (locale === "en") {
console.log("Pathname:", pathname);
routerNext.replace(pathname);
} else {
routerI18.replace(pathname, { locale });
}
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" aria-label={t("changeLanguage")}>
<Globe className="h-5 w-5 text-green-600 dark:text-green-400" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => changeLanguage({ locale: "en" })}>
English
</DropdownMenuItem>
<DropdownMenuItem onClick={() => changeLanguage({ locale: "hi" })}>
हिन्दी (Hindi)
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};
This works for static Rendering
by runnning npm run build the files are in diffent folder for the specified locale so i have extracted the default local i.e. en in the root directory
and localePrefix: "as-needed",
Here is how my folder look likes, "en" folder is empty
Check out the next-globe-gen package. It supports output: "export"
mode since it is not using any cookies or headers for adding i18n support for Next.js.
The basic idea of the package is to generate all locale routes so that there is no need for [locale]
dynamic route segment at all.
It also has a configuration option prefixDefaultLocale: false
which allows default locale content to be served from /
and others from /[locale]
.
Please modify the value of the localePrefix from "always" to "as-needed" in the middleware.ts or in navigation.ts if you use it
I had this issue, I did not want to use the url to set/change the language (ex: /en, /fr, /es), and also be able to read the language on server side components.
What I did was use the cookies to set the language, so the logic is something like this:
If language cookie doesn't exist, use next internationalization strategy to get the language, and pass it to the page in the header response.
Then in pages you can add some logic to read cookies and header, and language should be something like:
const lang = languageCookie.value ?? headerCookie
So my middleware looks something like:
const getLocale = (request: NextRequest) => {
const languageInCookie = request.cookies.get('language')
if (languageInCookie) {
console.log('---> Returning language from cookie', languageInCookie)
return languageInCookie.value
}
const headers = { 'accept-language': 'en-US,en;q=0.5' }
const languages = new Negotiator({ headers }).languages()
const locales = ['en', 'es', 'pt']
const defaultLocale = 'pt'
return match(languages, locales, defaultLocale)
}
export function middleware(request: NextRequest) {
const locale = getLocale(request)
console.log('-----> Locale from middleware', locale)
// Set header in middleware
return NextResponse.next({
headers: {
'x-language': locale
}
})
}
Now, in the page, it's important to set the cookie with expiration time, this will allow the cookie to be kept even when the browser is closed.
I used a server action:
'use server'
import { cookies } from 'next/headers'
export async function setLanguage(lang: string) {
cookies().set('language', lang, {
// Set cookie for 1 year
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
})
}
The cookie also can be changed in server components.
Now you can use a provider to allow use this language in client components, passing the server language within the main layout.
/en/example
to/example
then? For SEO purposes, you should not serve the same content twice. (Or if you really have to, use a canonical link) – Bergi Commented Jan 15, 2024 at 19:24