Make your Next.js project dynamic
This guide shows you how to connect an existing Next.js project to Prepr to retrieve and show Acme Lease demo content.
You can also watch the video for step-by-step instructions detailed in the guide below.
Connect your Next.js website to Prepr
The steps below continue from the previous section, Set up a Next.js project. If you don't yet have a Next.js website with static pages, follow the steps in this section first. Otherwise, let's get started.
Install the Apollo client
The Apollo client is an integration tool that helps to retrieve CMS data with GraphQL. The instructions below show you how to install the Apollo client so that you can add GraphQL queries to request data from the Prepr API.
-
Stop the localhost website server (
CTRL-C
) if it's running and execute the following command in the terminal:npm install @apollo/client @apollo/experimental-nextjs-app-support
-
Once done, create a file called
apollo-client.ts
in thesrc
folder. Copy the following code to this file to import and initialize the Apollo client:./src/apollo-client.tsimport {HttpLink} from "@apollo/client"; import { ApolloClient,InMemoryCache, registerApolloClient, } from "@apollo/client-integration-nextjs"; export const {getClient, query, PreloadQuery} = registerApolloClient(() => { return new ApolloClient({ cache: new InMemoryCache(), link: new HttpLink({ // this needs to be an absolute url, as relative urls cannot be used in SSR uri: process.env.PREPR_GRAPHQL_URL, // you can disable result caching here if you want to // (this does not work if you are rendering your page with `export const dynamic = "force-static"`) // fetchOptions: { cache: "no-store" }, }), }); });
This client will be used to make API requests to endpoints provided by the Prepr CMS across your Next.js application.
-
We recommend using environment variables to store sensitive information like access tokens. To add environment variables, create a
.env
file in the root directory of your project and add the access token like this:./.envPREPR_GRAPHQL_URL=<YOUR-PREPR-GRAPHQL-URL>
-
Replace the placeholder value
<YOUR-PREPR-GRAPHQL-URL>
with the API URL of an access token from Prepr. Get an access token by logging into your Prepr account:
a. Go to Settings → Access tokens to view all the access tokens.
b. Click to open the GraphQL Production access token, and copy the API URL to only retrieve published content items on your site for now. c. Paste the copied API URL in your.env
file.
Use the GraphQL Production access token to request published content items for your live app and use the GraphQL Preview token to make a preview of unpublished content items for your content editors.
-
Execute the following command to restart the server and refresh your page in the browser.
npm run dev
If your website runs without errors, then the setup above was done correctly. Now that the Apollo client is installed, you can retrieve content from Prepr. Before we create a query to retrieve content, let's follow some best practices for TypeScript and install a GraphQL code generator.
Install a code generator
In this step, you'll install a GraphQL code generator to generate TypeScript types automatically for your queries and keep these up to date with any schema changes. Check out the TypeScript best practices doc for more details. In the meantime, follow the steps below to install the GraphQL code generator.
-
Stop the localhost website server (
CTRL-C
) if it's running and execute the following commands in your terminal:npm install graphql npm install -D typescript @graphql-codegen/cli
-
Once done, initialize the setup wizard with the following command:
npx graphql-code-generator init
-
Choose the
Application built with React
option.
-
For the Where is your schema? question, use the same GraphQL URL you pasted in the
.env
for thePREPR_GRAPHQL_URL
variable. -
For the remaining questions, choose the options as shown in the image below.
-
Install additional plugins with the following command.
npm install @graphql-codegen/client-preset @graphql-codegen/typescript @graphql-codegen/typescript-react-apollo @graphql-codegen/typescript-resolvers
-
If successful, you'll see a new file,
codegen.ts
in the root of your project. Update it with the highlighted code below../codegen.tsimport type {CodegenConfig} from '@graphql-codegen/cli'; const config: CodegenConfig = { overwrite: true, schema: "<YOUR-PREPR-GRAPHQL-URL>", documents: ['!src/gql/**/*', 'src/queries/**/*.{ts,tsx}'], generates: { "src/gql/": { preset: 'client', plugins: [], presetConfig: { fragmentMasking: { unmaskFunctionName: 'getFragmentData' }, }, config: { reactApolloVersion: 3, }, }, "./graphql.schema.json": { plugins: ["introspection"] } } }; export default config;
-
Execute the following command to restart the server and refresh your page in the browser to make sure the Apollo client is installed correctly:
npm run dev
If your website runs without errors, then the setup above was done correctly. Now you can add a query to your project.
Add a GraphQL query
Once the GraphQL code generator is installed, you can add a query to get the content for your home page from Prepr.
If you’re using preloaded demo data in your Prepr environment as mentioned in the Prerequisites, you should have a Homepage content item like in the images below. The Homepage has all the elements of the page in a Stack field. The stack makes it easy for an editor to set up their page content in a flexible structure for the front end.
You can see that the hero and feature sections are in adaptive content blocks. We'll explain that in more detail in the upcoming personalization chapter.
Follow the steps below to add a query to retrieve the id, title, slug, and a stack containing the hero and feature sections with their own fields:
-
Create a
queries
folder in thesrc
directory of your project and create a file namedget-page-by-slug.ts
. -
Add the following query to this file to retrieve a page by its slug:
You'll notice that we've included fragments in our query. Fragments in GraphQL are a way to define a set of fields that can be reused.
import {graphql} from '@/gql'
// The GetPageBySlug query uses the input a slug parameter to retrieve a specific page
export const GET_PAGE_BY_SLUG = graphql(`
query GetPageBySlug($slug: String) {
Page(slug: $slug) {
title
_id
content {
__typename
... on Hero {
...Hero
}
... on Feature {
...Feature
}
}
}
}
`)
// A fragment for the feature section
export const FEATURE_SECTION_FRAGMENT = graphql(`
fragment Feature on Feature {
_id
heading
sub_heading
button {
...Button
}
_context {
variant_key
}
image_position
image {
url(width: 870, height: 570)
}
}
`)
// A fragment for the hero section
export const HERO_SECTION_FRAGMENT = graphql(`
fragment Hero on Hero {
_id
sub_heading
image {
url(preset: "Hero", width: 2000)
height
width
}
_context {
variant_key
}
heading
buttons {
...Button
}
}
`)
// A fragment for the button component
export const BUTTON_FRAGMENT = graphql(`
fragment Button on Button {
button_type
text
external_url
link {
... on Category {
_slug
}
... on Page {
_slug
}
... on Post {
_slug
}
}
}
`)
You can create and test GraphQL queries using the Apollo explorer (opens in a new tab) from Prepr. Open the API Explorer from the Homepage content item or from your access token page.
Generate TypeScript types
-
Now that the query is created, generate the TypeScript types by running the following command in the terminal:
npm run codegen
-
If the command is successful you'll see a new file called
graphql.ts
in thegql
folder. Check out the TypeScript doc for more details. -
Test the query by updating the
page.tsx
file. The code below executes the query and prints the query results to the console../src/app/[[...slug]]/page.tsx// Import the components to display them import HeroSection from "@/components/sections/hero-section"; import FeatureSection from "@/components/sections/feature-section"; import {getClient} from "@/apollo-client"; import {GetPageBySlugDocument, GetPageBySlugQuery} from "@/gql/graphql"; import {notFound} from "next/navigation"; export const revalidate = 0 export const dynamic = 'force-dynamic' async function getData(slug: string) { const {data} = await getClient().query<GetPageBySlugQuery>({ query: GetPageBySlugDocument, variables: { slug: slug, }, fetchPolicy: 'no-cache', }) if (!data) { return notFound() } return data } // Include parameters to check the slug in the URL export default async function Page({params}: { params: { slug: string | string[] } }) { let {slug} = await params // Set the slug to the home page slug if there's no slug if (!slug) { slug = '/' } // Add a forward slash to the slug to navigate to the correct page. if (slug instanceof Array) { slug = slug.join('/') } const data = await getData(slug) console.log(JSON.stringify(data.Page, undefined, 2)) return ( <div> <HeroSection/> <FeatureSection/> <FeatureSection align='right'/> <FeatureSection/> </div> ); }
When you check the console in your terminal, the response to the query looks something like this:
Now that the query has been created and retrieves the data successfully, fetch and show the specific page content from the query results.
Troubleshooting
Compilation error: In the page.tsx
for the line import {GetPageBySlugDocument, GetPageBySlugQuery} from "@/gql/graphql"
: '"@/gql/graphql"' has no exported member named 'GetPageBySlugDocument'
.
Cause: The query name was not defined or is different than expected in the page.tsx
.
Solution: Check that the query you created has a query name. In our example the query name is defined as query GetPageBySlug(...){...}
. After updating the query, regenerate the TypeScript types.
Fetch page content
To view the Prepr content in your website, you need to fetch the page elements. As mentioned previously, the page has elements in a Stack field. You'll fetch two components, a Hero section and Feature sections in the stack.
-
The components also include image content so you first need to set up your project to use the Next.js Image component by adding the image domain to the
next.config.ts
with the following code:./next.config.tsimport type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ images: { remotePatterns: [ { hostname: 'demo-patterns.stream.prepr.io', }, ], }, }; export default nextConfig;
The
hostname
matches the domain of the demo images included with the demo content you loaded in your environment. When you add your own images and other assets, don't forget to change thehostname
in the Next.js configuration to match the domain of your own images. -
Next, update your components to display data dynamically.
./src/components/sections/hero-section.tsximport Link from 'next/link' import { FragmentType, getFragmentData } from '@/gql' import { HERO_SECTION_FRAGMENT } from '@/queries/get-page-by-slug' import Image from 'next/image' import Button from '@/components/button' export default function HeroSection(props: { item: FragmentType<typeof HERO_SECTION_FRAGMENT> }) { const item = getFragmentData(HERO_SECTION_FRAGMENT, props.item) const image = item.image return ( <section className="bg-primary-50"> <div className="mx-auto max-w-8xl p-spacing flex flex-col items-center md:flex-row gap-8 py-10 lg:py-20"> <div className="basis-6/12"> <h1 className="text-mb-5xl lg:text-7xl text-secondary-700 font-medium break-words text-balance"> {item.heading} </h1> <p className="text-mb-lg text-secondary-500 lg:text-lg mt-4 lg:mt-6 text-balance"> {item.sub_heading} </p> <div className="flex gap-4 mt-8 xl:mt-10"> <div> <Link href='#'><Button>Find your car</Button></Link> </div> </div> </div> <div className="basis-6/12 relative flex justify-end items-center"> <div className="z-10 flex items-center aspect-[20/17] w-9/12 overflow-hidden justify-center absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"> <Image src={image?.url || ''} alt="Hero Image" width={720} height={360} className='object-cover rounded-2xl'/> </div> <div className="w-9/12 aspect-[20/17] bg-primary-100 rounded-3xl right-0 top-0 z-0"> </div> </div> </div> </section> ) }
-
Now you can fetch and set the data for the page by getting elements from the stack and returning them in the
page.tsx
file as follows:./src/app/[[...slug]]/page.tsx// Import the section components to display them import HeroSection from "@/components/sections/hero-section"; import FeatureSection from "@/components/sections/feature-section"; import {getClient} from "@/apollo-client"; import {GetPageBySlugDocument, GetPageBySlugQuery} from "@/gql/graphql"; import {notFound} from "next/navigation"; export const revalidate = 0 export const dynamic = 'force-dynamic' async function getData(slug: string) { const {data} = await getClient().query<GetPageBySlugQuery>({ query: GetPageBySlugDocument, variables: { slug: slug, }, fetchPolicy: 'no-cache', }) if (!data.Page) { return notFound() } return data } export default async function Page({ params}: {params: Promise<{ slug: string | string[]}>}) { let { slug} = await params // Set the slug to the home page value if there's no slug if (!slug) { slug = '/' } // Add a forward slash to the slug to navigate to the correct page. if (slug instanceof Array) { slug = slug.join('/') } const data = await getData(slug) const elements = data.Page?.content.map((element, index) => { if (element.__typename === 'Feature') { return <FeatureSection key={index} item={element}/> } else if (element.__typename === 'Hero') { return <HeroSection key={index} item={element}/> } }) return ( <div> {elements} </div> ); }
Now when you view the website on your localhost, you'll see something like the image below:
Troubleshooting
Compilation error: Line import {FragmentType, getFragmentData} from "@/gql";
in the components hero-section.tsx
and feature-section.tsx
-> '"@/gql"' has no exported member named 'FragmentType'.
Cause: The codegen.ts
does not have preset definition for query fragments.
Solution: Update the codegen.ts
to include the presets for fragments and regenerate the TypeScript types.
Congratulations! You have successfully connected your front end to Prepr to make your website dynamic. Continue your journey to the next section to set up data collection.
Was this article helpful?
We’d love to learn from your feedback