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.

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.

  1. Stop the localhost website server (CTRL-C) if it's running and execute the following command in the terminal:
npm install @apollo/client
  1. Once done, create a file called ApolloClient.ts in the src folder. Copy the following code to this file to import and initialize the Apollo client:
./src/ApolloClient.ts
import {HttpLink} from "@apollo/client";
import {ApolloClient, InMemoryCache, registerApolloClient,} from "@apollo/experimental-nextjs-app-support";
 
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.

  1. 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:
./.env
PREPR_GRAPHQL_URL=<YOUR-PREPR-GRAPHQL-URL>
  1. 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.

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.

access token

  1. 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.

  1. 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
  1. Once done, initialize the setup wizard with the following command:
npx graphql-code-generator init
  1. Choose the Application built with React option.

codegen wizard

  1. For the Where is your schema? question, use the same GraphQL URL you pasted in the .env for the PREPR_GRAPHQL_URL variable.

  2. For the remaining questions, choose the options as shown in the image below.

codegen wizard

  1. 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
  1. If successful, you'll see a new file, codegen.ts in the root of your project. Update it with the highlighted code below.
./codegen.ts
import 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;
  1. 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.

Header section in the Homepage content item

Home page content item header section

You can see that the header section is in an adaptive content block. We'll explain that in more detail in the upcoming personalization chapter.

Image and text section in the Homepage content item

Home page content item image and text section

Follow the steps below to add a query to retrieve the id, title, slug, and a stack containing the header and two image and text sections with their own fields:

  1. Create a queries folder in the src directory of your project and create a file named GetPageBySlug.ts.

  2. 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.

./src/queries/GetPageBySlug.ts
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) {
            _id
            title
            _slug
            stack {
                __typename
                ... on SectionImageAndText {
                    ...SectionImageAndTextFragment
                }
                ... on SectionHeader {
                    ...SectionHeaderFragment
                }
            }
        }
    }
`)
 
// A fragment for the image and text section
export const SECTION_IMAGE_AND_TEXT_FRAGMENT = graphql(`
    fragment SectionImageAndTextFragment on SectionImageAndText {
        _context {
            variant_id
            variant_key
        }
        title
        _id
        cta_button {
            ...CTAButtonFragment
        }
        align
    }
`)
 
// A fragment for the image and text section
export const SECTION_HEADER_FRAGMENT = graphql(`
    fragment SectionHeaderFragment on SectionHeader {
        _context {
            variant_id
            group_id
            variant_key
        }
        title
        image {
            url
            caption
        }
        cta_button {
            ...CTAButtonFragment
        }
        _id
    }
 
`)
 
// A fragment for the CTA button
export const CTA_BUTTON_FRAGMENT = graphql(`
    fragment CTAButtonFragment on CTAButton {
        link
        text
        internal_link {
            _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

  1. Now that the query is created, generate the TypeScript types by running the following command in the terminal:
npm run codegen
  1. If the command is successful you'll see a new file called graphql.ts in the gql folder. Check out the TypeScript doc for more details.

  2. 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 HeaderSection from "@/components/HeaderSection";
import ImageAndTextSection from "@/components/ImageAndTextSection";
import {getClient} from "@/ApolloClient";
import {GetPageBySlugDocument, GetPageBySlugQuery} from "@/gql/graphql";
import {notFound} from "next/navigation";
 
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} = 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>
          <HeaderSection/>
          <ImageAndTextSection/>
          <ImageAndTextSection align='right'/>
          <ImageAndTextSection/>
        </div>
    );
}

When you check the console in your terminal, the response to the query looks something like this:

query results in console

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 Header section and Image and Text sections in the stack.

  1. First, update your components to display data dynamically.
./src/components/HeaderSection.tsx
import Link from "next/link";
import {FragmentType, getFragmentData} from "@/gql";
import {SECTION_HEADER_FRAGMENT} from "@/queries/GetPageBySlug";
 
interface HeaderSectionProps {
    data: FragmentType<typeof SECTION_HEADER_FRAGMENT>
}
 
export default function HeaderSection(props: HeaderSectionProps) {
    const {title, image} = getFragmentData(SECTION_HEADER_FRAGMENT, props.data)
    
    return (
        <div className='px-4 md:px-10 lg:px-20'>
            <div className='mx-auto max-w-7xl w-full'
            >
                <div className='flex flex-wrap gap-y-8 py-16 md:flex-nowrap flex-shrink-0'>
                    <div
                        className='flex items-start justify-start flex-grow-0 flex-col space-y-16 md:basis-8/12 lg:basis-7/12'>
                        <h1 className='text-neutral-900 text-5xl font-medium lg:text-6xl'>
                            {title}
                        </h1>
                        {/* Searchbar */}
                        <div className='flex items-stretch justify-stretch w-full sm:max-w-xl rounded-full'>
                            <div
                                className='flex w-full justify-between rounded-l-full border-2 border-blue-600 bg-white px-4 py-2 lg:px-8 lg:py-4'>
                                <div
                                    className='flex w-full basis-5/12 items-center justify-between gap-3 md:gap-6 text-lg md:text-xl text-black hover:cursor-pointer lg:text-2xl'>
                                    <span>Brand</span>
                                    <TriangleDownIcon/>
                                </div>
                                <div className='mx-4 h-10 border-l-[1px] border-l-black lg:mx-5'></div>
                                <div
                                    className='flex basis-5/12 items-center justify-between gap-3 md:gap-6 text-lg md:text-xl text-black hover:cursor-pointer lg:text-2xl'>
                                    <span>Model</span>
                                    <TriangleDownIcon/>
                                </div>
                            </div>
                            <Link
                                href='/catalog'
                                className='flex items-center text-nowrap rounded-r-full bg-blue-600 px-4 text-base md:text-lg font-bold text-white hover:cursor-pointer hover:bg-blue-700 lg:px-8 lg:text-2xl'>
                                FIND A CAR
                            </Link>
                        </div>
                    </div>
                    <div className='flex w-full justify-end md:basis-6/12 lg:h-[23.75rem] lg:basis-7/12'>
                        <img
                            src={image?.url || ''}
                            alt={image?.caption || ''}
                            className='object-contain'
                        />
                    </div>
                </div>
            </div>
        </div>
    )
}
 
function TriangleDownIcon() {
    return (
        <svg
            xmlns='http://www.w3.org/2000/svg'
            width='18'
            height='18'
            viewBox='0 0 18 18'
            fill='none'>
            <path
                d='M9.86603 16.5C9.48112 17.1667 8.51888 17.1667 8.13398 16.5L2.0718 6C1.6869 5.33333 2.16802 4.5 2.93782 4.5L15.0622 4.5C15.832 4.5 16.3131 5.33333 15.9282 6L9.86603 16.5Z'
                fill='currentColor'
            />
        </svg>
    )
}
  1. 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 HeaderSection from "@/components/HeaderSection";
import ImageAndTextSection from "@/components/ImageAndTextSection";
import {getClient} from "@/ApolloClient";
import {GetPageBySlugDocument, GetPageBySlugQuery} from "@/gql/graphql";
import {notFound} from "next/navigation";
 
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
}
 
export default async function Page({params}: { params: { slug: string | string[] } }) {
    let {slug} = params
 
    if (!slug) {
        slug = '/'
    }
 
    if (slug instanceof Array) {
        slug = slug.join('/')
    }
 
    const data = await getData(slug)
    const elements = data.Page?.stack.map((element, index) => {
        if (element.__typename === 'SectionImageAndText') {
            return <ImageAndTextSection key={index} data={element}/>
        } else if (element.__typename === 'SectionHeader') {
            return <HeaderSection key={index} data={element}/>
        }
    })
 
    return (
        <div>
            {elements}
        </div>
    );
}
 

Now when you view the website on your localhost, you'll see something like the image below:

Dynamic home page

Troubleshooting
Compilation error: Line import {FragmentType, getFragmentData} from "@/gql"; in the components Header.tsx and ImageAndTextSection.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