Client-side routing for dynamic pages

commercetools Frontend uses the Next.js router for client-side routing, making it simple to create links to any dynamic page as long as the dynamic-page-handler has the logic to handle the created links.

In this article, we'll see how to create a dynamic page and link it to other pages on the frontend.

Let's modify our Star Wars example by adding 2 new pages:

  • A static page /star-wars/films showing a list of all the Star Wars films
  • A dynamic page /star-wars/film/[filmId] showing the details of the Star Wars film with filmId

Creating the film details page

  1. Add the logic to grab the filmId from the URL pattern /star-wars/film/[filmId] so it can be used to fetch the film information from SWAPI, to the dynamic-page-handler present in the backend/index.ts file

    Dynamic page handler implementation for the film details pagetypescript
    'dynamic-page-handler': async (request: Request): Promise<DynamicPageSuccessResult | null> => {
    const [_, filmId] = request.query.path.match(new RegExp('/star-wars/film/([^ /]+)'));
    if (filmId) {
    return await axios
    .post<DynamicPageSuccessResult>('https://swapi-graphql.netlify.app/.netlify/functions/index', {
    query: '{film(id:"' + filmId + '") {id, title, openingCrawl, releaseDate}}',
    })
    .then((response): DynamicPageSuccessResult => {
    return {
    dynamicPageType: 'example/star-wars-film-page',
    dataSourcePayload: response.data,
    pageMatchingPayload: response.data,
    };
    })
    .catch((err) => {
    return {
    dynamicPageType: 'example/star-wars-film-page',
    dataSourcePayload: { err },
    pageMatchingPayload: { err },
    };
    });
    }
    return null;
    },
  2. Create a custom Frontend component in the Studio with the following schema

    Schema for the example/star-wars-film Frontend componentjson
    {
    "tasticType": "example/star-wars-film",
    "name": "Star wars movie",
    "icon": "list",
    "category": "Documentation Examples",
    "schema": [
    {
    "name": "Configuration",
    "fields": [
    {
    "label": "film",
    "field": "film",
    "type": "dataSource",
    "dataSourceType": "example/star-wars-film",
    "required": true
    }
    ]
    }
    ]
    }

    To do that, go to the Components area in the Studio, click Create schema, paste the above JSON into the editor, then click Publish

  3. Create a React component in the tastics/star-wars/film/index.tsx that displays the film data passed to the page by the dynamic-page-handler

    React component to render film detailstypescript
    import React from 'react';
    const StarWarsFilmDetails = ({ data }) => {
    const film = data.data.film || {};
    return (
    <div>
    <h2>{film.title}</h2>
    <p> {film.openingCrawl}</p>
    <p>Released on {film.releaseDate}</p>
    </div>
    );
    };
    export default StarWarsFilmDetails;
  4. Register StarWarsFilm component in the tastics/index.tsx file

    Register the example/star-wars-film Frontend componenttypescript
    import NotFound from './not-found';
    import Markdown from './markdown/tastic';
    import StarWarsFilm from './star-wars/movie';
    export const tastics = {
    default: NotFound,
    'example/star-wars-film': StarWarsFilm,
    'training/content/markdown': Markdown,
    };
  5. From the Studio homepage, or the from the left menu, go to Developer > Dynamic pages.

  6. Click Create schema, then paste the following JSON into the editor, and click Publish.

    Dynamic page schema for film details pagejson
    {
    "dynamicPageType": "example/star-wars-film-page",
    "name": "Star wars film",
    "category": "Documentation Example",
    "icon": "stars",
    "dataSourceType": "example/star-wars-film",
    "isMultiple": true
    }

    You need to use the Production environment while creating the dynamic page.

  7. Go to the Dynamic pages section, select Star wars film, and create a New page version (testbed) in the Default page rule

    Site builder with Default page rule and testbed page version selected in the Live section.

  8. Use the Star Wars film component on the testbed page version as shown below

    Page builder with Star wars film component in the main section and Star wars film data source selected.

  9. Open <http://localhost:3000/star-wars/film/ZmlsbXM6Mg==>, the film detail page renders in the browser.

    Film details page rendered in the browser.

Now that the /star-wars/film/filmId page is working, let's create the star-wars/films page to list links to all films.

Creating the films list page

You need a data source extension to fetch the list of movies from the SWAPI and a custom Frontend component to render the list of movies for the films page.

  1. From the Studio homepage or the left menu, go to Developer > Data sources.

  2. Create the schema for the data source.

    Data source schema for the example/star-wars-all-films data sourcejson
    {
    "customDataSourceType": "example/star-wars-all-films",
    "name": "Star wars all films",
    "category": "Content",
    "icon": "source",
    "schema": []
    }
  3. Implement the data source extension in the backend/index.ts file

    Data source extension implementation for example/star-wars-all-filmstypescript
    'example/star-wars-all-films': async (
    config: DataSourceConfiguration,
    context: DataSourceContext,
    ): Promise<DataSourceResult> => {
    return await axios
    .post<DataSourceResult>('https://swapi-graphql.netlify.app/.netlify/functions/index', {
    query: '{allFilms { films {id, title, episodeID}} }',
    })
    .then((response) => ({
    dataSourcePayload: response.data,
    }))
    }
  4. Go to the Components area and create a schema that specifies the data source you created earlier in the schema field

    Schema for example/star-wars-all-filmsjson
    {
    "tasticType": "example/star-wars-all-films",
    "name": "Star wars films",
    "icon": "list",
    "category": "Documentation Examples",
    "schema": [
    {
    "name": "Configuration",
    "fields": [
    {
    "label": "films",
    "field": "films",
    "type": "dataSource",
    "dataSourceType": "example/star-wars-all-films",
    "required": true
    }
    ]
    }
    ]
    }
  5. Implement the React component in the index.ts, which renders links from the array of films provided by the data source extension

    React component to render list of filmstypescript
    import React from 'react';
    import Link from 'next/link';
    const StarWarsFilms = ({ data }) => {
    return (
    <div className={'pt-4'}>
    <h3 className={'mb-4'}>Star wars films</h3>
    {data.films.dataSource.data.allFilms.films.map((film) => (
    <Link key={film.id} href={`/star-wars/film/${film.id}`}>
    <a className={'block underline'}>{film.title}</a>
    </Link>
    ))}
    </div>
    );
    };
    export default StarWarsFilms;
  6. Register this component in the tastics/index.tsx file

    Register the example/star-wars-all-films Frontend componenttypescript
    import NotFound from './not-found';
    import Markdown from './markdown/tastic';
    import StarWarsFilms from './star-wars/all-films';
    import StarWarsFilm from './star-wars/film';
    export const tastics = {
    default: NotFound,
    'example/star-wars-all-films': StarWarsFilms,
    'example/star-wars-film': StarWarsFilm,
    'training/content/markdown': Markdown,
    };
  7. Go to the Site builder, click New, then Create page folder, input a name (we're calling ours Star wars)

  8. Select your new page folder, then click New, select Create page version, input a name (we're using films) and click Save

  9. Drag your component into the layout element, select the Star wars all films data source, and click Save

    Page builder with Star wars films film component in the main section and Star wars all films data source selected

  10. Open <http://localhost:3000/star-wars/films>, the films list renders in the browser.

    Films list rendered in the browser