Working with Webiny Headless CMS

Learn Webiny Next.js App

8
Lesson 8

Learn Webiny Next.js App

In this lesson, you'll build a real Next.js application that fetches and displays products from your Webiny Headless CMS.

In this lesson...

Here are the topics we'll cover

vpn_key

Create and secure API keys.

api

Build a reusable API client.

speed

Fetch data using Static Site Generation (SSG).

What you'll build:

A product listing homepage that fetches products from Webiny at build time and displays them as a simple list.

Prerequisites:

  • Completed Lessons 1-7 (especially Lesson 7: Reading Records via the API)
  • At least 2-3 published products in your Webiny CMS
  • Node.js 18+ and npm/yarn installed
  • Basic understanding of React and Next.js

Introduction

So far, you've learned how to use the API Playground to query your Webiny Headless CMS. Now it's time to put that knowledge into practice by building a real Next.js application!

In this lesson, we'll create a simple product listing page that fetches data from Webiny's Read API using Static Site Generation (SSG). This means:

  • Products are fetched once during build time
  • The result is embedded into static HTML
  • Users get instant page loads with no API calls
  • Perfect for product catalogs that don't change every second

This lesson establishes the foundation for the next lessons where we'll add a contact form (Lesson 9), implement lifecycle events (Lesson 10), and more!

Info:

We'll be building on this same Next.js application throughout lessons 8-12, so each lesson adds new functionality to the same codebase.

Project Setup

First, let's set up the Next.js project.

Step 1: Clone the Starter Repository

Clone the starter repository and install dependencies:

Terminal
Loading...

Step 2: Explore the Project Structure

The starter project has a basic Next.js 16 setup with TypeScript and Tailwind CSS:

Project Structure
Loading...

Step 3: Run the Development Server

Start the dev server to confirm everything works:

Terminal
Loading...

Open http://localhost:3000 in your browser. You should see a simple page with "Our Products" heading - that's your starting point!

Success:

Keep the dev server running while you work. It will auto-reload when you make changes.

Finding Your API URL

Before we can connect to Webiny, we need to find the API URL.

Open a terminal in your Webiny project directory (not the Next.js app) and run:

Terminal
Loading...

You'll see output similar to this:

Terminal output showing Webiny API URLs including Headless CMS Read API endpoint
Click to enlarge

Look for the Headless CMS - Read API URL in the output. It will look something like:

Example API URL
Loading...

Copy this URL - you'll need it in the next step.

Tip:

We covered this in Lesson 5, but here's a quick reminder: The Read API only returns published content, which is perfect for a public product listing page.

Creating an API Key

Now let's create an API key that allows the Next.js app to access your Webiny CMS.

Step 1: Navigate to API Keys

  1. Open the Webiny Admin in your browser
  2. Click Settings in the left sidebar
  3. Click API Keys

Step 2: Create a New API Key

Click the New API Key button in the top right.

Step 3: Configure the API Key

Fill in the following details:

  1. Name: Next.js App
  2. Description: API access for Next.js application
  3. Permissions: Click on Headless CMS to expand the section
API key form with Name, Description, and Permissions sections
Click to enlarge

Step 4: Set Permissions

Configure the permissions as follows:

  1. Access Level: Select Custom access
  2. GraphQL API Types:
    • Read (checked)
    • ❌ Preview (unchecked)
    • ❌ Manage (unchecked)
API key permissions configuration showing Headless CMS section with Read API enabled
Click to enlarge
Info:

We're giving the API key access to only the Read API because that's all we need for displaying published products. This follows the principle of least privilege - only grant the permissions you actually need. In Lesson 9, we'll expand this API key's permissions to include the Manage API for writing data.

Tip:

Note: Advanced permissions like "Content Model Groups", "Content Models", and "Primary Actions" are only available with the Advanced Access Control Layer (AACL), which requires a Business plan or higher. With the open-source version of Webiny, selecting "Read" API access gives full read access to all content.

Step 5: Save and Copy the Token

  1. Click Save API Key
  2. The form will be saved and your API Token will be displayed in the Token field
  3. Copy the token by clicking the copy icon - you'll need it in the next step!
API key form showing the generated token with copy button
Click to enlarge
Warning:

Keep your API token secure! Anyone with this token can access your Webiny CMS data according to the permissions you've set.

Setting Up Environment Variables

Now let's store the API URL and token securely.

Step 1: Create .env.local

In your Next.js project (not the Webiny project), create a new file called .env.local in the root directory:

.env.local
Loading...

Replace:

  • your-api-token-here with the token you copied in the previous step
  • https://your-url.cloudfront.net/cms/read with your actual Read API URL

Step 2: Restart the Dev Server

Environment variables are only loaded when the dev server starts, so you need to restart it:

  1. Stop the dev server (Ctrl+C or Cmd+C)
  2. Start it again: npm run dev
Warning:

Never commit .env.local to version control! It's already included in .gitignore by default, but always double-check before committing. API tokens should remain secret.

Creating TypeScript Types

Let's create TypeScript interfaces for the data we'll be working with.

Create a new file lib/types.ts:

lib/types.ts
Loading...

These interfaces match the structure of data returned by Webiny's Read API. Notice how:

  • User-defined fields are inside the values object
  • System fields like id and entryId are at the top level
  • We have an error field (best practice from Lesson 6!)
  • We have separate interfaces for list responses and single-item responses

Creating the API Client

Now let's create a reusable API client for communicating with Webiny.

Create a new file lib/webiny.ts:

lib/webiny.ts
Loading...

Let's break down what this code does:

  1. Environment Variables: Loads the API URL and token from .env.local
  2. Error Checking: Throws an error if environment variables are missing
  3. fetchWebiny Function: Generic function that:
    • Makes a POST request to Webiny
    • Adds authentication headers
    • Handles HTTP errors
    • Handles GraphQL-level errors (syntax, validation)
    • Returns typed data
  4. getProducts Function:
    • Defines the GraphQL query with /* GraphQL */ comment for syntax highlighting
    • Same query we used in Lesson 7!
    • Calls fetchWebiny with proper types
    • Handles Webiny-level errors (from the error field)
    • Returns array of products
Info:

Remember from Lesson 7: There are two types of errors: - GraphQL errors (top-level errors array) - syntax/validation issues - Webiny errors (inside error field) - business logic issues Our API client handles both!

Fetching Data in the Homepage

Now let's use our API client to fetch products and display them!

Update app/page.tsx:

app/page.tsx
Loading...

Let's understand what's happening here:

  1. Async Server Component: The component is async, which allows us to use await directly
  2. Fetch Products: We call getProducts() from our API client
  3. Error Handling: We wrap the fetch in a try/catch to handle any errors gracefully
  4. Conditional Rendering:
    • Show error message if fetch failed
    • Show "no products" message if the array is empty
    • Show product list if we have products
  5. Product Display: For each product we show:
    • Name (heading)
    • Price (formatted with dollar sign)
    • Description
    • SKU (small text)
Success:

This is a Server Component, which means it runs on the server (or at build time). The Webiny API is called during the build process, not in the browser!

Testing in Development

Save all your files and check your browser at http://localhost:3000.

You should see your products displayed! If you don't see products or see an error, check the troubleshooting section below.

Building for Production

Now let's see SSG (Static Site Generation) in action!

Step 1: Build the Application

Run the build command:

Terminal
Loading...

You should see output similar to this:

Build Output
Loading...

The ○ (Static) indicator means the page was statically generated at build time. This is exactly what we want!

Step 2: Start the Production Server

Terminal
Loading...

Step 3: Test the Production Build

Open http://localhost:3000 again. The page should load instantly because it's serving pre-built HTML with embedded product data!

Info:

In production, the Webiny API is called only once during npm run build. Users get instant page loads because the HTML is already generated with all product data included.

How Static Site Generation Works

Let's understand what just happened:

  1. Build Time: When you ran npm run build:

    • Next.js executed your Server Component
    • The getProducts() function called Webiny's Read API
    • Products were fetched from your CMS
    • The response was embedded into static HTML
    • HTML was saved to disk
  2. Request Time: When a user visits the page:

    • Next.js serves the pre-built HTML immediately
    • No API calls to Webiny happen
    • Page loads instantly (no loading spinners!)
    • Users see the product data from build time
  3. Updating Content:

    • If you publish new products in Webiny, they won't appear immediately
    • You need to rebuild the app: npm run build
    • Or use Incremental Static Regeneration (ISR) - covered in advanced topics

Why use SSG for a product catalog?

  • Performance: Instant page loads
  • SEO: Content is in HTML for search engines
  • Reliability: Works even if Webiny API is temporarily down
  • Cost: Fewer API calls = lower bandwidth costs
  • ⚠️ Tradeoff: Content is slightly stale (not real-time)

For most product catalogs, having content that's a few minutes or hours old is perfectly acceptable!

Troubleshooting

"Missing required environment variables" Error

Problem: The app crashes with an error about missing environment variables.

Solution:

  1. Verify .env.local exists in your project root (not in a subdirectory)
  2. Check that both WEBINY_READ_API_URL and WEBINY_API_TOKEN are set
  3. Restart your dev server: Ctrl+C then npm run dev

"Unauthorized" or "403 Forbidden" Error

Problem: API returns an authentication error.

Solution:

  1. Check your API token is copied correctly in .env.local
  2. Verify the API key has Read API permission enabled
  3. Make sure you're using the Read API URL (ends with /cms/read)
  4. Try creating a new API key and updating .env.local

"No products found" Message

Problem: The page loads but shows "No products found".

Solution:

  1. Verify you have products published in Webiny (not just saved as drafts)
  2. Check you're using the Read API (only returns published content)
  3. Verify your API key has permission to access the Product model
  4. Test the query in the API Playground (Lesson 6) to confirm it works

GraphQL Syntax Error

Problem: Error message about GraphQL syntax.

Solution:

  1. Check for typos in your GraphQL query in lib/webiny.ts
  2. Make sure field names match your Product model exactly
  3. Copy the query from Lesson 7's API Playground where it worked

Build Fails

Problem: npm run build fails with an error.

Solution:

  1. Verify your API URL and token are correct
  2. Check your internet connection (build needs to reach Webiny API)
  3. Look at the error message - it will tell you what failed
  4. Test in development mode first (npm run dev)

Summary

Congratulations! You've built a real Next.js application that fetches and displays content from Webiny Headless CMS!

In this lesson, you learned how to:

  • ✅ Create API keys with specific permissions (Read-only access)
  • ✅ Securely store API credentials using environment variables
  • ✅ Write a reusable API client with proper error handling
  • ✅ Create TypeScript types for Webiny data structures
  • ✅ Fetch data from Webiny in Next.js Server Components
  • ✅ Use Static Site Generation (SSG) for optimal performance
  • ✅ Display CMS content with proper error handling
  • ✅ Understand the difference between build-time and request-time execution

What's Next?

In the next lesson (Writing Data), we'll add a contact form to this application and learn how to write data back to Webiny using mutations. You'll create a new ContactSubmission content model and use the Manage API to save form submissions!

Success:

Want to see the complete code? Check out the completed example repository on the completed branch.

?

It's time to take a quiz!

Test your knowledge and see what you've just learned.

When using Static Site Generation (SSG), when is the Webiny API called to fetch products?


Next lesson: Writing Data - Create a contact form and learn how to write data to Webiny using mutations.

Use Alt + / to navigate