Describe your question/issue in detail
We're working with a project which requires internationalisation and have been looking through a variety of documents to determine how this works in Prismic (sources in next section).
After enabling en-gb
and a custom locale value we've got the locales configured in nextjs.config.mjs
as expected. When running locally if we call http://localhost:3000/ we get a scenario where a 404 is thrown by NextJS before it hits the middleware and subsequently seems to terminate any remaining processes.
[next] GET / 404 in 237ms
[next] CONSOLE LOG: MIDDLEWARE TEST
[next] GET /favicon.ico 200 in 5ms
If we call http://localhost:3000/en-gb the same issue occurs and it never reaches the console logs in the page.tsx
files. The only time it does seem to trigger semi-correctly is when it's called as http://localhost:3000/en_gb which is obviously incorrect and results in this expected error.
[next] MIDDLEWARE TEST
[next] CONSOLE LOG: Page Content
[next] { lang: 'en_gb' }
[next] CONSOLE LOG: Page Meta Data
[next] { lang: 'en_gb' }
[next] ⨯ Error: Some(en_gb) is not a valid language code
[next] at async Index (./src/app/[lang]/page.tsx:47:18)
[next] digest: "1732355935"
[next] ⨯ Error: Some(en_gb) is not a valid language code
[next] at async Index (./src/app/[lang]/page.tsx:47:18)
[next] digest: "1732355935"
[next] CONSOLE LOG: Page Meta Data
[next] { lang: 'en_gb' }
[next] ⨯ Error: Some(en_gb) is not a valid language code
[next] at async Module.generateMetadata (./src/app/[lang]/page.tsx:27:18)
[next] digest: "3879409013"
[next] ⨯ Error: Some(en_gb) is not a valid language code
[next] at async Module.generateMetadata (./src/app/[lang]/page.tsx:27:18)
[next] digest: "3879409013"
[next] GET /en_gb 500 in 948ms
[next] MIDDLEWARE TEST
[next] GET /favicon.ico 200 in 4ms
When we've added in a work around for the above example (.replace
on the underscore to convert it to a hyphen) we know the languages are available as we've got them embedded on the page. These link to the correct places as expected but clicking on any of the links results in a 404 being thrown.
Any advice on fixing this issue would be greatly appreciated.
What steps have you taken to resolve this issue already?
Investigated the following articles and forum posts (amongst others):
- nextjs-i18n
- Internationalization implementation with Next JS (13.5.6) & typescript
- nextjs-starter-prismic-multi-language
Code
I'm unable to provide you with a link to a GitHub repository as the code is sat behind a private repo so the following information is the best I can provide.
Directory Structure
- src
- app
- [lang]
- [uid]
- page.tsx
- article
- page.tsx
- page.tsx
- slice-simulator
- [uid]
- api
- favicon.ico
- icon.png
- layout.tsx
- [lang]
- app
next.config.mjs
/** @type {Promise<import('next').NextConfig>} */
const nextConfig = async () => {
const client = createClient(sm.repositoryName);
const { languages } = await client.getRepository();
const locales = languages.map((lang) => lang.id);
const defaultLocale = languages.find(({ is_master }) => is_master).id;
return {
i18n: { locales, defaultLocale: defaultLocale, localeDetection: false },
// Other configuration
};
};
export default nextConfig;
prismic.ts
// Out of the box imports and exports
const routes: prismic.ClientConfig['routes'] = [
{ type: 'home', path: '/:lang?', uid: 'home' },
{ type: 'page', path: '/:lang?/:uid' },
{ type: 'article', path: '/:lang?/article/:uid' },
];
// Out of the box createClient function
middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/prismicio';
export async function middleware(request: NextRequest) {
const client = createClient();
const repository = await client.getRepository();
const locales = repository.languages.map((lang) => lang.id);
const defaultLocale = locales[0];
// Check if there is any supported locale in the pathname
const { pathname } = request.nextUrl;
const pathnameIsMissingLocale = locales.every(
(locale) =>
!pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`,
);
// Redirect to default locale if there is no supported locale prefix
if (pathnameIsMissingLocale) {
return NextResponse.rewrite(
new URL(`/${defaultLocale}${pathname}`, request.url),
);
}
}
export const config = {
matcher: ['/((?!_next).*)'],
};
app/page.tsx
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
console.log('Language Meta Data');
console.log(params);
const client = createClient();
const home = await client.getSingle('home', { lang: params.lang });
return {
title: prismic.asText(home.data.title),
description: home.data.meta_description,
openGraph: {
title: home.data.meta_title ?? undefined,
images: [{ url: home.data.meta_image.url ?? '' }],
},
};
}
export default async function Index({ params }: PageProps) {
console.log('Language Page');
console.log(params);
const client = createClient();
const page = await client.getSingle('home', { lang: params.lang });
const { slices } = page.data;
return (
<Layout language={params.lang} page={page}>
<SliceZone slices={slices} components={components} />
</Layout>
);
}
[uid]/page.tsx
export async function generateMetadata({
params: { lang, uid },
}: PageProps): Promise<Metadata> {
const client = createClient();
const page = await client
.getByUID('page', uid, { lang: lang })
.catch(() => notFound());
return {
title: prismic.asText(page.data.title),
description: page.data.meta_description,
openGraph: {
title: page.data.meta_title || undefined,
images: [
{
url: page.data.meta_image.url || '',
},
],
},
};
}
export default async function Page({ params: { lang, uid } }: PageProps) {
const client = createClient();
const page = await client
.getByUID('page', uid, { lang: lang })
.catch(() => notFound());
return (
<Layout language={lang} page={page}>
<SliceZone slices={page.data.slices} components={components} />
</Layout>
);
}
export async function generateStaticParams() {
const client = createClient();
const pages = await client.getAllByType('page', { lang: '*' });
return pages.map(({ uid, lang }) => ({ uid: uid, lang: lang }));
}
package.json
{
"name": "nextjs-starter-prismic-minimal-ts",
"version": "0.1.0",
"private": true,
"license": "Apache-2.0",
"author": "Prismic <contact@prismic.io> (https://prismic.io)",
"scripts": {
"dev": "concurrently \"npm:next:dev\" \"npm:slicemachine\" --names \"next,slicemachine\" --prefix-colors blue,magenta",
"next:dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"slicemachine": "start-slicemachine",
"format": "prettier --write ."
},
"dependencies": {
"@prismicio/client": "^7.5.0",
"@prismicio/next": "^1.5.0",
"@prismicio/react": "^2.7.4",
"classnames": "^2.5.1",
"next": "^14.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"uuid": "^9.0.1"
},
"devDependencies": {
"@slicemachine/adapter-next": "^0.3.38",
"@svgr/webpack": "^8.1.0",
"@types/node": "^20.12.11",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^9.0.8",
"concurrently": "^8.2.2",
"eslint": "^8",
"eslint-config-next": "^14.2.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.2",
"prettier": "^3.2.5",
"sass": "^1.77.1",
"slice-machine-ui": "^1.26.0",
"typescript": "^5.4.5"
}
}