---
title: Quick start guide: Next.js and React
slug: graph/docs/quick-start-guide-next-js-and-react
docTags: 
createdAt: 2026-07-30T20:30:19.179Z
---

Integrate a [Next.js](https://nextjs.org/docs) project with Optimizely Content Management System (SaaS) through [Apollo Client](https://www.apollographql.com/docs/react). This guide configures a GraphQL client with cached templates, creates reusable components that fetch content and landing pages, and renders the results in your application. The result is type-safe data access, optimized queries, and a streamlined developer experience for managing content in CMS (SaaS).

## Prerequisites

Before you begin, ensure you have the following:

- Node.js (v18 or higher).
- An Optimizely CMS (SaaS) instance.
- API key and Optimizely Graph endpoint.

## Create a Next.js project

Scaffold the Next.js app that hosts the Apollo Client and React components.

```bash
npx create-next-app@latest my-saas-cms-site
cd my-saas-cms-site
```

## Install GraphQL dependencies

Add the Apollo Client and GraphQL runtime to the project.

```bash
npm install @apollo/client graphql
```

## Configure Apollo Client with cached templates

Create the Apollo Client and point it at your Optimizely Graph endpoint with the `cg-stored-query` header so repeat requests reuse cached templates.

Create a file named `lib/apollo-client.ts` and add the following.

```typescript
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';

const httpLink = new HttpLink({
  uri: 'https://beta.cg.optimizely.com/content/v2?auth=YOUR_ACCESS_KEY&stored=true',
  headers: {
    // Enable cached templates for better performance
    'cg-stored-query': 'template',
  }
});

const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      errorPolicy: 'all',
    },
    query: {
      errorPolicy: 'all',
    },
  },
});

export default client;
```

## Configure environment variables

Store the Optimizely Graph endpoint in an environment variable so it stays out of source code and is straightforward to swap per environment.

Create a `.env.local` file and add the following.

```bash
NEXT_PUBLIC_OPTIMIZELY_GRAPH_ENDPOINT=https://beta.cg.optimizely.com/content/v2?auth=YOUR_ACCESS_KEY&stored=true
```

## Create your first component

Build a React component that issues a `GetContent` query against `_Content`, renders a list of items, and surfaces loading and error states.

Create a file named `components/ContentList.tsx` and add the following.

```typescript
import { gql } from '@apollo/client';
import { useQuery } from '@apollo/client/react';

const GET_CONTENT = gql`
query GetContent($locale: [Locales], $skip: Int = 0, $limit: Int = 10) {
  _Content(
    locale: $locale
    orderBy: { _metadata: { lastModified: DESC } }
    skip: $skip
    limit: $limit
  ) {
    items {
      _metadata {
        displayName
        url {
          default
        }
        types
        published
      }
    }
    total
  }
}
`;

export function ContentList() {
  const { loading, error, data } = useQuery(GET_CONTENT, {
    variables: { locale: ['en', 'NEUTRAL'] },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Latest content from CMS (SaaS)</h1>
      <p>Total content items: {data._Content.total}</p>

      {data._Content.items.map((item, index) => (
        <div
          key={index}
          style={{
            marginBottom: '1rem',
            padding: '1rem',
            border: '1px solid #ccc',
          }}
        >
          <h3>{item._metadata.displayName}</h3>
          <p>
            <strong>Type:</strong> {item._metadata.types?.join(', ')}
          </p>
          <p>
            <strong>URL:</strong> {item._metadata.url?.default}
          </p>
          <p>
            <strong>Published:</strong>{' '}
            {new Date(item._metadata.published).toLocaleDateString()}
          </p>
          {item._metadata.url?.default && (
            <a
              href={item._metadata.url.default}
              target="_blank"
              rel="noopener noreferrer"
            >
              View content →
            </a>
          )}
        </div>
      ))}
    </div>
  );
}
```

## Create a landing page component

Build a second component that targets the `LandingPage` content type so you can render marketing pages alongside the generic content list.

Create a file named `components/LandingPageList.tsx` and add the following.

```typescript
import { gql } from '@apollo/client';
import { useQuery } from '@apollo/client/react';

const GET_LANDING_PAGES = gql`
  query GetLandingPages($locale: Locales) {
    LandingPage(locale: [$locale]) {
      items {
        _metadata {
          displayName
          url {
            default
          }
          published
        }
      }
      total
    }
  }
`;

export function LandingPageList() {
  const { loading, error, data } = useQuery(GET_LANDING_PAGES, {
    variables: { locale: 'en' },
  });

  if (loading) return <p>Loading landing pages...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>Landing pages</h2>
      <p>Total landing pages: {data.LandingPage.total}</p>
      {data.LandingPage.items.map((page, index) => (
        <div
          key={index}
          style={{
            marginBottom: '1rem',
            padding: '1rem',
            border: '1px solid #ccc',
          }}
        >
          <h3>{page._metadata.displayName}</h3>
          <p>
            <strong>URL:</strong> {page._metadata.url?.default}
          </p>
          <p>
            <strong>Published:</strong>{' '}
            {new Date(page._metadata.published).toLocaleDateString()}
          </p>
          {page._metadata.url?.default && (
            <a
              href={page._metadata.url.default}
              target="_blank"
              rel="noopener noreferrer"
            >
              View page →
            </a>
          )}
        </div>
      ))}
    </div>
  );
}
```

## Use the components in your page

Wrap the app in an `ApolloProvider` and render the two components on the home page so they share the configured client.

In `app/page.tsx`, import and render the components.

```typescript
'use client';
import { ApolloProvider } from '@apollo/client/react';
import client from '../lib/apollo-client';

import { ContentList } from '../components/ContentList';
import { LandingPageList } from '../components/LandingPageList';

export default function Home() {
  return (
    <ApolloProvider client={client}>
      <div style={{ padding: '2rem' }}>
      <h1>Mosey Bank CMS content</h1>
      <ContentList />
      <hr style={{ margin: '2rem 0' }} />
      <LandingPageList />
      </div>
    </ApolloProvider>
  );
}
```

## Run and verify

Start the development server and confirm that Optimizely Graph returns content for the two components.

```bash
npm run dev
```

Open `http://localhost:3000/` in your browser to see your content and landing pages loaded from Optimizely Graph.
