Reference
Fontdue.jsGraphQLDemo templateGuides
WebflowWordpress + Lay ThemeWordpress + SempliceNext.js (App Router)Next.js (Pages Router)Information
Full-service website developmentManaging your font catalogVariable fontsFont licensesWebfontsCross-origin accessPayments with StripeTaxTest fontsTest modeManage your Fontdue subscriptionLaunching your siteTroubleshootingCustomize the Address fieldsUpdates
Whatβs newUpdate 001Update 002Update 003Update 004Update 005Next.js (App Router)
In this guide weβll create an example Fontdue integration for Next.js App Router, using the Fontdue.js React components and querying the GraphQL API for data.
If you prefer to use the Next.js pages router, see our old documentation here.
Start from our example repo
Start from scratch
Weβll get started with a default Next.js installation using TypeScript:
npx create-next-app@latestWe 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)? β¦ Yes
β Would you like to customize the default import alias? β¦ NoAllow 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@latestCreate a file .env.local:
NEXT_PUBLIC_FONTDUE_URL=https://your-site.fontdue.comReplace your-site.fontdue.com with your Fontdue URL.
Modify src/app/layout.tsx to add the FontdueProvider, StoreModal and the fontdue.css:
// src/app/layout.tsx
import FontdueProvider from "fontdue-js/FontdueProvider";
import StoreModal from "fontdue-js/StoreModal";
import "fontdue-js/fontdue.css";
import "./globals.css";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<FontdueProvider>
{children}
<StoreModal />
</FontdueProvider>
</body>
</html>
);
}Add Fontdue components
Modify src/app/pages.tsx and try adding a Fontdue component, for example:
// src/app/pages.tsx
import TypeTesters from "fontdue-js/TypeTesters";
import styles from "./page.module.css";
export default function Home() {
return (
<main className={styles.main}>
<TypeTesters collectionSlug="ibm-plex" autofit />
</main>
);
}(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 this example function for fetching Fontdue data.
// src/lib/graphql.ts
import { promises as fs } from "fs";
import crypto from "crypto";
import path from "path";
const ENDPOINT = `${process.env.NEXT_PUBLIC_FONTDUE_URL}/graphql`;
const getStaticQuery = async (queryName: string) => {
let query = await fs.readFile(
path.resolve(process.cwd(), "src", "queries", queryName),
"utf8"
);
return query;
};
const fetchGraphql = async <Q, V = void>(
queryName: string,
variables: V | void
): Promise<Q> => {
const hash = crypto
.createHash("md5")
.update(`${JSON.stringify(variables)}`)
.digest("hex")
.slice(0, 6);
const query = await getStaticQuery(queryName);
const response = await fetch(`${ENDPOINT}?query=${queryName}&hash=${hash}`, {
method: "POST",
body: JSON.stringify({ query, variables }),
headers: {
"content-type": "application/json",
},
next: {
tags: ["graphql"],
},
});
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 { fetchGraphql, getStaticQuery };
We can use these functions to query some data for the index.
Modify src/app/page.tsx importing the new functions and fetching data at the top of the component. This takes advantage of Nextβs new server component fetching.
// src/app/page.tsx
import TypeTesters from "fontdue-js/TypeTesters";
import styles from "./page.module.css";
import { fetchGraphql } from "@/lib/graphql";
export default async function Home() {
const data = await fetchGraphql("Index.graphql");
return (
<main className={styles.main}>
<TypeTesters collectionSlug="00-hypercube" autofit />
</main>
);
}Then add Index.graphql. For example:
# src/queries/Index.graphql
query Index {
viewer {
fontCollections(onlyRoots: true, first: 10) {
edges {
node {
id
name
slug {
name
}
}
}
}
}
}You should see response data for the query logged now.
Note though that the data has the type unknown. We can get TypeScript types for our GraphQL queries using GraphQL code generator.
GraphQL Code Generator
Itβs possible to generate the TypeScript types for all of the queries we specify in the src/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 dotenvAdd a codegen.yml file with this configuration.
schema: ${NEXT_PUBLIC_FONTDUE_URL}/graphql
documents: './src/queries/*.graphql'
generates:
operations-types.ts:
config:
onlyOperationTypes: true
preResolveTypes: true
skipTypename: true
avoidOptionals: true
plugins:
- typescript
- typescript-operationsUpdate package.json to add codegen to the "scripts" section. We use dotenv to insert our Fontdue URL environment variable.
"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 src/app/page.tsx component:
import { IndexQuery } from '../../operations-types';Optionally, provide a path alias for this in tsconfig.json:
{
"compilerOptions": {
...
"paths": {
"@/*": ["./src/*"],
"@graphql": ["./operations-types.ts"]
}
},
...
}import { IndexQuery } from '@graphql';At this stage you have the tools to build out all your pages consuming data from your Fontdue API. The examples below provide some more guidance on how you might query data for common use-cases.
Create links to fonts pages
Our example Index query fetches font collections along with their slugs, so we can create a list of links to their respective font pages:
// src/app/page.tsx
import Link from "next/link";
import styles from "./page.module.css";
import { fetchGraphql } from "@/lib/graphql";
import { IndexQuery } from "@graphql";
export default async function Home() {
const data = await fetchGraphql<IndexQuery>("Index.graphql");
return (
<main className={styles.main}>
<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>
</main>
);
}URL params in queries
Letβs now add a detail page for fonts, which we link to from the homepage.
Weβll add a few files.
# src/queries/Font.graphql
query Font ($slug: String!) {
viewer {
slug(name: $slug) {
fontCollection {
id
name
}
}
}
}# src/queries/FontPaths.graphql
query FontPaths {
viewer {
fontCollections(first: 999, onlyRoots: true) {
edges {
node {
slug { name }
}
}
}
}
}// src/lib/utils.ts
export function notEmpty<TValue>(
value: TValue | null | undefined
): value is TValue {
return value !== null && value !== undefined;
}// src/app/fonts/[slug]/page.tsx
import React from "react";
import TypeTesters from "fontdue-js/TypeTesters";
import BuyButton from "fontdue-js/BuyButton";
import { FontPathsQuery, FontQuery, FontQueryVariables } from "@graphql";
import { fetchGraphql } from "@/lib/graphql";
import { notEmpty } from "@/lib/utils";
interface FontProps {
params: { slug: string };
}
export default async function Font({ params }: FontProps) {
const data = await fetchGraphql<FontQuery, FontQueryVariables>(
"Font.graphql",
{
slug: params.slug as string,
}
);
const font = data.viewer.slug!.fontCollection!;
return (
<div>
<h1>{font.name}</h1>
<BuyButton collectionId={font.id} collectionName={font.name} />
<TypeTesters collectionId={font.id} autofit />
</div>
);
}
export async function generateStaticParams() {
const data = await fetchGraphql<FontPathsQuery>("FontPaths.graphql");
const slugs = data
.viewer!.fontCollections!.edges!.map((edge) => edge?.node?.slug?.name)
.filter(notEmpty);
return slugs.map((slug) => ({ slug }));
}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 dynamic path src/app/fonts/[slug]/page.tsx 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 instead query like so:
fontCollections(collectionTypes: [SUPERFAMILY, FAMILY], first: 999)Loading webfonts
The latest Next.js conventions for loading fonts (using the next/font packages) do not allow us to conveniently use the webfonts automatically generated by Fontdue. But we created a workaround: a component that you can use to load the webfonts for any FontStyle from the GraphQL API. After we query for the webfontSources, we can use ReactDOM.preload() which will create relevant <link rel="preload"> elements in the pageβs <head>, ensuring webfonts are loaded early. We then add a <style> element inline in the page to define the @font-face declarations, the same way the Fontdue CSS files do, but without requiring an extra request.
// src/components/PreloadWebfonts.tsx
"use client";
import ReactDOM from "react-dom";
interface FontStyleCSS {
cssFamily: string | null;
name: string | null;
webfontSources:
| ({
url: string | null;
format: string | null;
} | null)[]
| null;
}
const createFontFaceStyle = ({
cssFamily,
name,
webfontSources,
}: FontStyleCSS): string => {
const source = webfontSources?.find((source) => source?.format === "woff2");
if (!source) return "";
return `
@font-face {
font-family: "${cssFamily} ${name}";
src: url(${source.url}) format(${source.format});
font-weight: 400;
font-style: normal;
}
`;
};
export default function PreloadWebfonts({
style,
}: {
style: FontStyleCSS | null;
}) {
if (!style) return null;
const source = style.webfontSources?.find(
(source) => source?.format === "woff2"
);
if (source?.url) {
ReactDOM.preload(source.url, { as: "font" });
}
return (
<style
type="text/css"
dangerouslySetInnerHTML={{
__html: createFontFaceStyle(style),
}}
/>
);
}Usage
Weβll create a realistic Homepage where we list all the font collections, rendered in their own βFeature Styleβ (selected in the Fontdue admin).
First, in our Index.graphql file, we query for all root font collections and their associated featureStyle, including the webfontSources which weβll pass to the PreloadWebfonts component.
# src/queries/Index.graphql
query Index {
viewer {
fontCollections(onlyRoots: true, first: 99) {
edges {
node {
name
featureStyle {
cssFamily
name
webfontSources {
format
url
}
}
}
}
}
}
}We add a wrapper component to render the font style, using Fontdue useFontStyle hook, which renders the loading state as a series of dots.
// src/components/FontStyle.tsx
"use client";
import React from "react";
import useFontStyle from "fontdue-js/useFontStyle";
interface FontStyle_props {
familyName: string | null | undefined;
styleName: string | null | undefined;
style?: React.CSSProperties;
children: React.ReactNode;
}
export default function FontStyle({
familyName,
styleName,
style: styleProp,
children,
}: FontStyle_props) {
const { style } = useFontStyle({
fontFamily: `${familyName} ${styleName}`,
fontWeight: "400",
fontStyle: "normal",
});
return <span style={{ ...style, ...styleProp }}>{children}</span>;
}Now, in our home page we can combine these components to render each font in its own featureStyle
// src/app/page.tsx
import Link from "next/link";
import { fetchGraphql } from "@/lib/graphql";
import { IndexQuery } from "@graphql";
import FontStyle from "@/components/FontStyle";
import PreloadWebfonts from "@/components/PreloadWebfonts";
export default async function Home() {
const data = await fetchGraphql<IndexQuery>("Index.graphql");
return (
<main className="main">
<section className="home">
{data.viewer.fontCollections?.edges?.map((edge) => {
const node = edge!.node!;
if (!node.slug) return;
return (
<h2 key={node.id}>
<PreloadWebfonts style={node.featureStyle} />
<Link href={`/fonts/${node.slug.name}`}>
<FontStyle
familyName={node.featureStyle?.cssFamily}
styleName={node.featureStyle?.name}
>
{node.name}
</FontStyle>
</Link>
</h2>
);
})}
</section>
</main>
);
}Revalidating data
All of the fetch queries we make are automatically cached by Next.js. In development, itβs possible to refresh the cache just by telling the browser to not cache (e.g. ββ§R) but in production we need to empty the cache whenever content changes. We can create an endpoint in our Next.js app that Fontdue can call whenever you make any changes in your Fontdue admin. Note that the endpoint must accept POST requests.
// src/app/api/revalidate/route.ts
import { NextRequest, NextResponse } from "next/server";
import { revalidateTag } from "next/cache";
export async function POST(_request: NextRequest) {
revalidateTag("graphql");
return NextResponse.json({ revalidated: true, now: Date.now() });
}Then, in your Fontdue admin, navigate to Settings β Website settings, find the field Deploy hook URL and enter the endpoint URL like below. Replace the domain name with your siteβs production domain.
https://your-site.vercel.app/api/revalidate- Next.js (App Router)
- Start from scratch
- Allow API access
- Add Fontdue.js
- Add Fontdue components
- Query the GraphQL API
- Query functions
- GraphQL Code Generator
- Create links to fonts pages
- URL params in queries
- Loading webfonts
- Usage
- Revalidating data