There are docs pages about i18n routing and redirects and special support for locale parameters (which I have not used personally).
In a more general sense, it sounds like what you want is optional catch all routes.
You can define a file in your foo directory with the name [[...slug]].js. The params which correspond to a path like /foo/us/en is { slug: ["us", "en"] } where each segment of the path becomes an element of the slug array.
You can use getStaticPaths to generate all of the known country/language pairs. Setting fallback: true allows for the user to enter another combo and not get a 404.
export const getStaticPaths = async () => {
return {
paths: [
{ params: { slug: ["us", "en"] } },
{ params: { slug: ["us", "es"] } },
{ params: { slug: ["ca", "en"] } },
{ params: { slug: ["ca", "fr"] } },
/*...*/
],
fallback: true, // allows unknown
};
};
As far as redirection, it depends on whether you want an actual redirect such that typing in /foo leads to /foo/us/en or if those are two separate pages which show the same content. I'm going to assume that we want an actual redirect.
You'll convert from slug to props in your getStaticProps function. This is also where you implement your redirects. I'm going to assume that you have (or can create) some utility functions like isValidCountry(country) and getDefaultLanguage(country)
export const getStaticProps = async ( context ) => {
const [country, language] = context.params?.slug ?? [];
// if there is no country, go to us/en
if (!country || !isValidCountry(country)) {
return {
redirect: {
statusCode: 301, // permanent redirect
destination: "/foo/us/en",
},
};
}
// if there is no language, go to the default for that country
if (!language || !isValidLanguage(language, country)) {
return {
redirect: {
statusCode: 301, // permanent redirect
destination: `/foo/${country}/${getDefaultLanguage(country)}`,
},
};
}
// typical case, return country and language as props
return {
props: {
country,
language,
},
};
};
There are things that you can do in the component itself with useRouter and isFallback, but I'm not sure if it's needed. In dev mode at least I'm getting proper redirects.
/foo/ca/en - ok
/foo/ca/fr - ok
/foo/ca/xx - redirects to /foo/ca/en
/foo/ca - redirects to /foo/ca/en
/foo - redirects to /foo/us/en
Complete code with TypeScript types:
import { GetStaticPaths, GetStaticProps } from "next";
export interface Props {
country: string;
language: string;
}
export default function Page({ country, language }: Props) {
return (
<div>
<h1>
{country} - {language}
</h1>
</div>
);
}
const pairs = [
["us", "en"],
["us", "es"],
["ca", "en"],
["ca", "fr"],
];
const isValidCountry = (c: string) => pairs.some(([cc]) => cc === c);
const isValidLanguage = (l: string, c: string) =>
pairs.some(([cc, ll]) => cc === c && ll === l);
const getDefaultLanguage = (c: string) =>
pairs.find(([cc]) => cc === c)?.[1] ?? "en";
export const getStaticProps: GetStaticProps<Props, { slug: string[] }> = async (
context
) => {
const [country, language] = context.params?.slug ?? [];
// if there is no country, go to us/en
if (!country || !isValidCountry(country)) {
return {
redirect: {
statusCode: 301, // permanent redirect
destination: "/foo/us/en",
},
};
}
// if there is no language, go to the default for that country
if (!language || !isValidLanguage(language, country)) {
return {
redirect: {
statusCode: 301, // permanent redirect
destination: `/foo/${country}/${getDefaultLanguage(country)}`,
},
};
}
// typical case, return country and language as props
return {
props: {
country,
language,
},
};
};
export const getStaticPaths: GetStaticPaths<{ slug: string[] }> = async () => {
return {
paths: pairs.map((slug) => ({
params: { slug },
})),
fallback: true, // allows unknown
};
};
/foo/check/you redirect to/foo/check/1(Considering 1 as your default value), or if the user goes to/foo/check/876(Consider 876 an out of range value) you redirect to/foo/check/1?/foo/[country]/[language]. In case of default language being 'en' we would like to have this value passed to router when user navigates to/foo/[country]/(let's say/foo/UK/)