πŸ““

Next.js (Pages Router)

Log in Start free trial β†’

Next.js (Pages router)

In this guide we’ll create an example Fontdue integration for Next.js Page Router, using the Fontdue.js React components and querying the GraphQL API for data.

🚧
This guide is intended for web developers. If you're a type designer interested in building your own site from scratch, we recommend either finding a web developer to work with or inquiring with us about our web development service. Learn more

We’ll get started with a default Next.js installation using TypeScript:

npx create-next-app

We used the following settings:

βœ” What is your project named? … my-app
βœ” Would you like to use TypeScript with this project? … Yes
βœ” Would you like to use ESLint with this project? … Yes
βœ” Would you like to use Tailwind CSS with this project? … No
βœ” Would you like to use `src/` directory with this project? … Yes
βœ” Use App Router (recommended)? … No
βœ” Would you like to customize the default import alias? … No

Allow API access

When you start the server with npm run dev, your site will be served on http://localhost:3000. Add this URL to your Fontdue Cross-origin API access setting.

Add Fontdue.js

For details about the React components, read the docs on NPM β†’
npm install fontdue-js@latest

Create a file .env.local:

NEXT_PUBLIC_FONTDUE_URL=https://your-site.fontdue.com

Replace your-site.fontdue.com with your Fontdue URL.

Modify pages/_app.tsx to add the FontdueProvider, StoreModal and the fontdue.css:

import '@/styles/globals.css'
import type { AppProps } from 'next/app';
import FontdueProvider from 'fontdue-js/FontdueProvider';
import StoreModal from 'fontdue-js/StoreModal';
import 'fontdue-js/fontdue.css';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <FontdueProvider url={process.env.NEXT_PUBLIC_FONTDUE_URL!}>
      <Component {...pageProps} />
      <StoreModal />
    </FontdueProvider>
  );
}

export default MyApp;

Add Fontdue components

Modify pages/index.tsx and try adding a Fontdue component, for example:

import TypeTesters from 'fontdue-js/TypeTesters';

const Home: NextPage = () => {
  return (
    <div>
      {...}
	    
      <TypeTesters collectionSlug="ibm-plex" />
    </div>
  );  
};

(Replace ibm-plex with a font collection slug from your site)

It’s possible to stop here and use your own solution for managing data on your site. The next section shows how you can consume all the content from the Fontdue CMS to power your site.

Query the GraphQL API

The following provides an example setup for querying the Fontdue GraphQL API for static site generation using TypeScript.

Query functions

Add these example functions for making queries.

lib/fetchGraphql.ts

const ENDPOINT = `${process.env.NEXT_PUBLIC_FONTDUE_URL}/graphql`;

const fetchGraphql = async <Q, V = void>(
  query: string,
  variables: V | void
): Promise<Q> => {
  const response = await fetch(ENDPOINT, {
    method: 'POST',
    body: JSON.stringify({ query, variables }),
    headers: {
      'content-type': 'application/json',
    },
  });

  if (response.status !== 200) {
    throw new Error('Fontdue request failed');
  }

  const json = await response.json();

  const errorMessage = json.errors?.[0]?.message;
  if (errorMessage) {
    throw new Error(`Fontdue graphql request error: ${errorMessage}`);
  }

  return json.data;
};

export default fetchGraphql;

lib/graphql.ts

import { promises as fs } from 'fs';
import path from 'path';
import fetchGraphql from './fetchGraphql';

export const getStaticQuery = async (queryFile: string) => {
  return await fs.readFile(
    path.resolve(process.cwd(), 'queries', queryFile),
    'utf8'
  );
};

export { fetchGraphql };

We can use these functions to query some data for the index.

Modify pages/index.tsx importing the new functions and adding the getStaticProps to the bottom of the file.

import { getStaticQuery, fetchGraphql } from '../lib/graphql';

export async function getStaticProps() {
  const query = await getStaticQuery('Index.graphql');
  const data = await fetchGraphql<IndexQuery>(query);
  return {
    props: { data },
  };
}

We need to do a few things to make this work. First, we need to add the file queries/Index.graphql. For example:

query Index {
  viewer {
    fontCollections(onlyRoots: true, first: 10) {
      edges {
        node {
          id
          name
          slug {
            name
          }
        }
      }
    }
  }
}

The other thing missing is the IndexQuery type which we’ll get into next.

GraphQL Code Generator

It’s possible to generate the TypeScript types for all of the queries we specify in the queries directory, so that we get proper types for use in our components. Let’s set that up:

npm install --save-dev graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations dotenv

Add a codegen.yml file with this configuration.

schema: ${NEXT_PUBLIC_FONTDUE_URL}/graphql
documents: './queries/*.graphql'
generates:
  operations-types.ts:
    config:
      onlyOperationTypes: true
      preResolveTypes: true
      skipTypename: true
      avoidOptionals: true
    plugins:
      - typescript
      - typescript-operations

Update package.json to add codegen to the "scripts" section.

"codegen": "DOTENV_CONFIG_PATH=.env.local graphql-codegen -r dotenv/config --watch"

You may want to incorporate the codegen into the dev script, for example:

npm install --save-dev npm-run-all
"dev": "run-p next-dev codegen",
"next-dev": "next dev",
"codegen": "DOTENV_CONFIG_PATH=.env.local graphql-codegen -r dotenv/config --watch"

With the codegen script running, we should see a new file in the root directory: operations-types.ts. This will include an IndexQuery type.

Note, the name of the type comes from the name of the query inside Index.graphql (not the name of the file).

Let’s import the type in our pages/index.tsx component:

import { IndexQuery } from '../operations-types';

Render data from the GraphQL API

In the getStaticProps function, we’re providing the query response as a prop data to the Home component. Let’s update pages/index.tsx to be a list of the names of our fonts with links.

import Link from 'next/link';

interface HomeProps {
  data: IndexQuery;
}

const Home: NextPage<HomeProps> = ({ data }) => {
  return (
    <ul>
      {data.viewer?.fontCollections?.edges?.map((edge) => (
        <li key={edge!.node!.id}>
          <Link href={`/fonts/${edge!.node!.slug?.name}`}>
            {edge!.node!.name}
          </Link>
        </li>
      ))}
    </ul>
  );
};

URL params in queries

Let’s add a detail page for fonts, which we link to from the homepage.

We’ll add a few files.

queries/Font.graphql

query Font ($slug: String!) {
  viewer {
    slug(name: $slug) {
      fontCollection {
        id
        name
      }
    }
  }
}

queries/FontPaths.graphql

query FontPaths {
  viewer {
    fontCollections(first: 99, onlyRoots: true) {
      edges {
        node {
          slug { name }
        }
      }
    }
  }
}

lib/utils.ts

export function notEmpty<TValue>(
  value: TValue | null | undefined
): value is TValue {
  return value !== null && value !== undefined;
}

pages/fonts/[slug].tsx

import React from 'react';
import TypeTesters from 'fontdue-js/TypeTesters';
import BuyButton from 'fontdue-js/BuyButton';
import {
  FontPathsQuery,
  FontQuery,
  FontQueryVariables,
} from '../../operations-types';
import { fetchGraphql, getStaticQuery } from '../../lib/graphql';
import { notEmpty } from '../../lib/utils';

interface Font_props {
  data: FontQuery;
}

const Font: React.FC<Font_props> = ({ data }) => {
  const font = data.viewer!.slug!.fontCollection!;
  
  return (
    <div>
      <h1>{font.name}</h1>
      <BuyButton collectionId={font.id} collectionName={font.name} />
      <TypeTesters collectionId={font.id} />
    </div>
  );
};

export default Font;

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const query = await getStaticQuery('Font.graphql');
  const data = await fetchGraphql<FontQuery, FontQueryVariables>(query, {
    slug: params!.slug as string,
  });

  return {
    props: { data },
  };
};

export const getStaticPaths: GetStaticPaths = async () => {
  const query = await getStaticQuery('FontPaths.graphql');
  const data = await fetchGraphql<FontPathsQuery>(query);
  const slugs = data
    .viewer!.fontCollections!.edges!.map((edge) => edge?.node?.slug?.name)
    .filter(notEmpty);

  return {
    paths: slugs.map((slug) => ({ params: { slug } })),
    fallback: false,
  };
};

You should now be able to navigate on the site using links to the font detail pages. πŸŽ‰

This example demonstrates how you can pass the slug param from the fonts/[slug].tsx page template through to the Font query’s $slug variable.

This also demonstrates how to provide Next with a list of font slugs, in order to statically generate all font pages. To do that, we’re querying the GraphQL API for all of our relevant font collections. Notice how we’re querying for onlyRoots: true, which matches with how we’re querying for collections in the Index query. You may want detail pages for all collections including subfamilies, in which case you should query like so:

fontCollections(collectionTypes: [SUPERFAMILY, FAMILY], first: 999)

Deploying on Vercel

Deploy your app on Vercel as a standard Next.js project with the environment variable NEXT_PUBLIC_FONTDUE_URL pointing to your Fontdue URL. (When you’re ready to launch your site, reach out and we’ll update your Fontdue URL to a subdomain of your primary domain).

Deploy hooks

Whenever your Fontdue content changes, we need to instruct Vercel to rebuild the project static HTML pages. Navigate to Project Settings β†’ Git β†’ Deploy Hooks, and create a hook with the name Fontdue Content Change (for example) and your primary git branch (e.g. main), and click Create Hook. Copy the URL value and then navigate to your Fontdue admin console β†’ Settings β†’ Website Settings β†’ Deploy Hook URL, and paste. Now any time you save a change in the Fontdue admin, the Vercel project will rebuild.