Client-side routing for dynamic pages

Frontastic 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

Before we get started, create a sandbox in your Frontastic studio project and use this Frontastic sandbox instance for the local development.

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': 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;
  },
  1. Create a custom Frontastic component in frontend/frontastic/tastics/star-wars/film/ with the following schema
{
    "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
                }
            ]
        }
    ]
}
  1. Upload this component schema to the Components area of the Frontastic studio
38403840
  1. 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
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;
  1. Register StarWarsFilm component in the tastics/index.tsx file
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,
};
  1. Create a dynamic page schema and upload it to the Developer > Dynamic pages section on the Frontastic studio
{
    "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.

38403840
  1. Go to the Dynamic pages section, select Star wars film, and create a New page version (testbed) in the Default page rule
38403840
  1. Use the Star Wars film component on the testbed page version as shown below
38403840
  1. Open http://localhost:3000/star-wars/film/ZmlsbXM6Mg==, and the film detail page should open up as shown below
34503450

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 Frontastic component to render the list of movies for the films page.

  1. Create the schema for the data source
{
    "customDataSourceType": "example/star-wars-all-films",
    "name": "Star wars all films",
    "category": "Content",
    "icon": "source",
    "schema": []
}
  1. Upload the data source schema to the Developer > Data sources section in the Frontastic studio
38403840
  1. Implement the data source extension in the backend/index.ts file
'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,
        }))
    }
  1. Create a schema.json for the Frontastic component in frontend/frontastic/tastics/star-wars/all-films/ and specify the data source you created earlier in the schema field
{
    "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
                }
            ]
        }
    ]
}
  1. Upload this component schema to the Components section of the Frontastic studio
38403840
  1. Implement the React component in the index.ts, which renders links from the array of films provided by the data source extension
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;
  1. Register this component in the tastics/index.tsx file
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,
};
  1. From the Frontastic studio, create a New page folder called Star wars and add a New page folder to it called films
38403840
  1. Create a New page version with the name films as shown in the image above and use the StarWarsFilms component along with the example/star-wars-all-films data source filter
38403840
  1. Open http://localhost:3000/star-wars/film/ZmlsbXM6Mg==, and the film detail page should open up as shown below
38303830

Let's see the complete flow in action:


Did this page help you?