Arkadiusz Kałkus
Arkadiusz Kałkus

Reputation: 18443

Default dynamic route in Next.js

Next.js enables us to define dynamic routes in our apps using the brackets [param]. It allows us to design URL in such a way that for example language is passed as parameter. When no route matches the user is redirected to error page.

The idea is simple and documentation abut dynamic routes in Next.js is rather limited. Does anybody know if it's possible to assign a default value to dynamic route parameter?

Upvotes: 1

Views: 4450

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42288

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
  };
};

Upvotes: 2

Related Questions