Log in Start free trial →
Reference
Guides
Information
Updates
© Fontdue, 2022
Next.js
In this guide we’ll create an example Fontdue integration for Next.js, using the Fontdue.js React components and querying the GraphQL API for data.
We’ll get started with a default Next.js installation using TypeScript:
yarn create next-app --typescript
By default, Next.js installs the latest version of React (18). Fontdue.js does not yet support React 18, so we must change the version to 17:
"react": "^17.0.0",
"react-dom": "^17.0.0"
Allow API access
When you start the server with yarn dev
, your site will be served on https://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 →
yarn add fontdue-js
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 type { AppProps } from 'next/app';
import FontdueProvider from 'fontdue-js/FontdueProvider';
import StoreModal from 'fontdue-js/StoreModal';
import 'fontdue-js/fontdue.css';
import '../styles/globals.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:
yarn add graphql @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations
Add a codegen.yml
file with this configuration. Replace example.fontdue.com
with your Fontdue URL.
schema: https://example.fontdue.com/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": "graphql-codegen --watch"
You may want to incorporate the codegen into the dev
script, for example:
yarn add npm-run-all
"dev": "run-p next-dev codegen",
"next-dev": "next dev",
"codegen": "graphql-codegen --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)