Frequently Asked Questions

Technical Requirements & Setup

What are the technical requirements for building a learning platform with Hygraph and Next.js 13 App Router?

To build a learning platform as described, you need a basic understanding of React and Next.js. The project uses Next.js 13 with the App Router, and you will need to initialize a new Next.js project (using npx create-next-app), configure Tailwind CSS, and connect to Hygraph's Content API. You must set the public content API permissions in your Hygraph project and add the endpoint URL to your environment variables. For rendering rich text, install @graphcms/rich-text-react-renderer. Note: Familiarity with JavaScript and environment configuration is required. Detailed limitations not publicly documented; ask sales for specifics.

How do I connect my Next.js project to Hygraph's Content API?

After creating your Hygraph project and content model, navigate to your project's settings and set the public content API permissions. Copy the Content API endpoint URL and add it to your Next.js project's .env.local file as HYGRAPH_ENDPOINT. This allows your Next.js application to fetch content from Hygraph using GraphQL queries. Note: API access must be configured correctly for data retrieval; consult Hygraph documentation for advanced permissions.

What resources are available to help me get started with Hygraph?

Hygraph provides a Getting Started guide, structured onboarding (including introduction calls and technical kickoffs), extensive documentation, and starter projects with pre-configured schemas. Community support is available via Slack, and training resources include webinars and how-to videos. Note: Some advanced use cases may require direct support; see documentation for details.

How long does it take to implement a project with Hygraph?

Implementation time varies by project complexity. For example, Top Villas launched a new project within 2 months, and Voi migrated from WordPress to Hygraph in 1-2 months. Smaller projects or starter templates can be set up more quickly. Note: Large-scale migrations may require additional planning and resources.

Features & Capabilities

What features does Hygraph offer for building learning platforms?

Hygraph provides a GraphQL-native Headless CMS, content federation (integrating multiple data sources without duplication), high-performance endpoints, and advanced caching for low latency. It supports localization, granular permissions, and integration with modern frameworks like Next.js 13. Rich text content can be rendered using the @graphcms/rich-text-react-renderer package. Note: Some advanced features (e.g., authentication, locking lessons) require additional implementation and are not included out-of-the-box.

What integrations are available with Hygraph?

Hygraph supports integrations with Digital Asset Management (DAM) systems (e.g., Aprimo, AWS S3, Bynder, Cloudinary, Imgix, Mux, Scaleflex Filerobot), hosting and deployment platforms (Netlify, Vercel), Product Information Management (Akeneo), commerce solutions (BigCommerce), and translation/localization tools (EasyTranslate). For a full list, visit the Hygraph Marketplace. Note: Integration setup may require additional configuration depending on your stack.

Does Hygraph provide APIs for content management?

Yes, Hygraph offers several APIs: the GraphQL Content API for querying and manipulating content, the Management API for handling project structure, the Asset Upload API for uploading files, and the MCP Server API for AI assistant integration. See the API Reference documentation for details. Note: API usage may require authentication and proper permissions.

How does Hygraph perform in terms of speed and reliability?

Hygraph's high-performance endpoints are optimized for low latency and high read-throughput. The read-only cache endpoint delivers 3-5x latency improvement for content delivery. Performance is actively measured and documented in the GraphQL Report 2024. Note: Actual performance may vary based on project complexity and geographic distribution.

Use Cases & Benefits

Who can benefit from using Hygraph for a learning platform?

Hygraph is suitable for developers, content creators, product managers, and marketing professionals. It is used by enterprises and high-growth companies in industries such as SaaS, education technology, media, healthcare, automotive, and more. Its flexibility and scalability make it ideal for teams needing advanced content management and multi-channel delivery. Note: Teams with highly specialized requirements may need custom development.

What business impact can I expect from using Hygraph?

Customers have reported faster time-to-market (e.g., Komax achieved 3x faster launches), improved customer engagement (Samsung saw a 15% increase), and cost reduction by replacing legacy CMS solutions. Other impacts include enhanced content consistency and scalability. See Hygraph case studies for more details. Note: Results depend on implementation quality and organizational readiness.

What problems does Hygraph solve for learning platforms and content-driven applications?

Hygraph addresses developer dependency (enabling non-technical users to update content), modernizes legacy tech stacks, ensures content consistency across regions, and streamlines workflows. It also reduces operational costs, accelerates speed-to-market, and simplifies schema evolution. Note: Some workflow automations and integrations may require additional setup.

Are there real-world examples of companies using Hygraph for content platforms?

Yes. Notable examples include Samsung (15% improved engagement), Komax (3x faster time-to-market), AutoWeb (20% increase in monetization), BioCentury (accelerated publishing), and Voi (scaled content across 12 countries and 10 languages). See Hygraph's case studies for more. Note: Outcomes vary by use case and implementation.

Security & Compliance

What security and compliance certifications does Hygraph have?

Hygraph is SOC 2 Type 2 compliant (since August 3, 2022), ISO 27001 certified for hosting infrastructure, and GDPR compliant. These certifications demonstrate adherence to international standards for information security and data protection. Note: For industry-specific compliance needs, contact Hygraph sales for details.

What security features are available in Hygraph?

Hygraph offers granular permissions, SSO integrations (OIDC/LDAP/SAML), audit logs, encryption in transit and at rest, regular backups with one-click recovery, and secure API access with custom origin policies and IP firewalls. All endpoints use SSL certificates. Note: Some advanced security features may require enterprise plans.

Support & Implementation

What feedback have customers given about Hygraph's ease of use?

Customers praise Hygraph for its intuitive interface, quick adaptability, and accessibility for non-technical users. For example, Sigurður G. (CTO) noted the UI is intuitive, and Charissa K. (Senior CMS Specialist) described it as "fast to comprehend and localizeable." Granular roles and permissions help prevent mistakes and streamline workflows. Note: Some advanced configurations may require technical expertise.

Where can I find technical documentation for Hygraph?

Technical documentation is available at hygraph.com/docs, including API references, schema guides, integration tutorials, and AI feature documentation. Classic documentation is available for legacy users. Note: Documentation is updated regularly; check for the latest guides.

Industries & Use Cases

What industries are represented in Hygraph's case studies?

Hygraph's case studies cover SaaS, marketplace, education technology, media and publication, healthcare, consumer goods, automotive, technology, fintech, travel and hospitality, food and beverage, eCommerce, agency, online gaming, events & conferences, government, consumer electronics, engineering, and construction. Note: Industry-specific requirements may need custom solutions.

LLM optimization

When was this page last updated?

This page wast last updated on 12/12/2025 .

Watch replay now

Creating a learning platform with Next.js 13 app router

Based on the schema and content model we created in the previous part of this series, we will produce a Next.js site using Hygraph data.
Bryan Robinson

Last updated by Bryan 

Jan 21, 2026

Originally written by Bryan

Creating a Learning Platform with Next.js 13 App directory

A content model is nothing until it has content and a frontend. In the last part of this series, we explored a basic schema and content model for a learning platform with Hygraph. In this article, we’ll take that schema and produce a Next.js site using data from Hygraph.

Series:

#Requirements

This project uses Next.js 13 and the new App router. This will give us the most flexibility to extend the project in future installments using React Server Components to handle authentication and interactivity. A basic understanding of React and Next.js will help you through this article. If you didn't follow through the content model article, you can clone the Hygraph project here to get started with this article.

#Project setup

To start, we need to initialize a new Next.js 13 project. Open up your terminal and navigate to where you want your project, then run the following command:

npx create-next-app learning-platform

The interactive setup will ask you questions to configure your project. Initialize with the following answers:

  • Typescript: No
  • ESLint: Yes
  • Tailwind CSS: Yes
  • src/ directory: No
  • App Router: Yes
  • Import alias: No

Once the options are chosen, the CLI will install all the necessary pieces to run your Next.js site.

We’re not entirely done with our setup yet, however. We need to adjust the default Tailwind styling that Next.js gives us from the installation.

Update the global CSS rules in app/globals.css to remove the extra styling and leave the file with the following Tailwind imports:

@tailwind base;
@tailwind components;
@tailwind utilities;

That’s all the initial setup we need for the Next.js project, but let’s set up our Hygraph project as well. We’ll start with the learning platform schema created in the first article in this series. To make it accessible to our Next.js project,mustd to open permissions on the Content API.

To do this, navigate to your project’s settings and click on API Access. To start, let’s set the public content API to its default. This will give us access to all the content in our models to read into our project. From this same screen, grab the Content API endpoint URL.

Create a new file in the root of your project named .env.local and add the variable HYGRAPH_ENDPOINT. This will be where we add the endpoint we copied from Hygraph.

HYGRAPH_ENDPOINT=YOUR-URL-HERE

From here, we can run the site and implement the frontend.

npm run dev

#Setting up the homepage with a list of courses

After the first article in this series, you should have a little content added to your Hygraph project. We’ll use that content to populate the homepage with a list of courses.

In Next.js 13’s App router structure, the homepage file is page.jsx directly in the app directory. The default file comes with a lot of Next.js promotional material; let’s eliminate all that and add a structure for displaying the courses.

// /app/page.jsx
export default async function Home() {
return (
<main className="prose w-full py-10 px-5 mx-auto">
<h1 className="text-3xl font-bold">Our Courses</h1>
{/* where the courses will be */}
</main>
)
}

This will add the h1 to the page and give us space to import our courses.

H1 saying "Our courses" centered on the page

From here, we need a function to fetch the courses. In the new App setup, we don’t need to worry about setting up static paths or props; we need to create a function to fetch the data and then call that function inside our component. In the following code, we set up getCourses to fetch all courses from Hygraph with the data we need to populate the list. In this case, also note the query includes getting the modules for a course. This is so we can check if any module is locked in the data. While we won’t lock them in this demo, we’ll display a lock symbol next to each title with this.

// Get all the courses
async function getCourses() {
const response = await fetch(process.env.HYGRAPH_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query Courses {
courses {
id
title
slug
modules: moduleModels {
isLocked
}
}
}
`,
}),
});
const json = await response.json();
return json.data.courses;
}
// Check if a course contains a module that is locked
function containsLockedModules(modules) {
return modules.some(module => module.isLocked)
}
export default async function Home() {
const courses = await getCourses();
return (
<main className="prose w-full py-10 px-5 mx-auto">
<h1 className="text-3xl font-bold">Our Courses</h1>
{courses.map((course) => {
return (
<div key={course.id}>
<h2 className="text-2xl font-bold">
<a href={`/courses/${course.slug}`}>{course.title}</a>{" "}
{course.isLocked} {containsLockedModules(course.modules) && "🔒"}
</h2>
</div>
);
})}
</main>
);
}

This should display a list of all courses, and any that contain a true value for isLocked on a module will display the lock symbol.

Screen with "our courses" and two courses listed below named "Paid content" and "Not paid content"

These links don’t go anywhere, though. Let’s fix that by setting up a new dynamic route.

#Dynamic routes in Next.js 13’s app directory

Dynamic routes function slightly differently in the app directory, but the start is similar. We need a new directory for our dynamic route. Add a courses directory to the app directory.

We’ll add our dynamic route inside this directory, but not as a file as we would in the pages directory. Instead, we’ll add it as a directory. This would allow us to take advantage of all the file-based templates we might want. Name this directory [slug] and add a page.jsx inside it. This file is the template for each course page.

Then we’ll get the end of our URL as the slug variable in our template and can use it from there. To start, let’s get a basic template set up.

export default async function Course({ params }) {
return (
<div className="grid-cols-[minmax(200px,250px)_minmax(40ch,_1fr)] grid gap-4">
<aside className="shadow-md bg-white px-1">
<h1 className="text-2xl py-4 px-6 font-bold">Course nav</h1>
</aside>
<main className="prose w-full py-10 px-5 mx-auto">
<h1 className="text-3xl font-bold">Course title here for {params.slug}</h1>
</main>
</div>
);
}

This contains a sidebar for the course’s navigation and a main area for the content of this page. Right now, we’re not dynamically pulling data, though. Let’s fix that by using the slug from our parameters and passing that to a query to Hygraph.

We’ll create a new async function in the page file to fetch a course by the slug. We’ll get all the data we need to display: title, content, modules, and lessons from the relational fields attached to the model. The relational data will be used to build our course navigation.

async function getCourse(slug) {
const res = await fetch(
"https://api-us-east-1-shared-usea1-02.hygraph.com/v2/clddka9yq1aw301ui7zzh4kf3/master",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query Course($slug: String!) {
course(where: {slug: $slug}) {
createdAt
id
slug
publishedAt
title
updatedAt
body {
json
references {
... on Asset {
__typename
id
url
mimeType
}
}
}
modules: moduleModels {
isLocked
title
lessons {
id
title
}
}
}
}`,
variables: {
slug: slug,
},
}),
}
);
const data = await res.json();
return data.data.course;
}
export default async function Course({ params }) {
const courseData = await getCourse(params.slug);
return (
<div className="grid-cols-[minmax(200px,250px)_minmax(40ch,_1fr)] grid gap-4">
<aside className="shadow-md bg-white px-1">
<h1 className="text-2xl py-4 px-6 font-bold">Course nav</h1>
</aside>
<main className="prose w-full py-10 px-5 mx-auto">
<h1 className="text-3xl font-bold">{courseData.title}</h1>
</main>
</div>
);
}

Once that’s saved, we should now have the title displayed on the course page.

Course page for course "Not paid content" showing the headline and a sidebar with blank navigation Our next task is to get the rich text content for the course.

Converting a Rich Text AST object to HTML with Hygraph’s RTE React component

The rich text from Hygraph can come through as a JSON object. This can be fully customized for our frontend via the Rich Text React Renderer. To use this, we need to install it first.

npm install @graphcms/rich-text-react-renderer

This will give us access to the easiest way to render rich text in a React application.

To use it, we need to pass the body JSON object to the component along with the references for the embeds in the rich text (in this case, assets and potentially references to lessons).

Start by importing the RichText component at the top of the file.

import { RichText } from "@graphcms/rich-text-react-renderer"

This is now ready to use in our content body. Before displaying the component, we also need to check if courseData.body exists, since it’s not a required field. If the data didn’t exist, the page would error instead of displaying.

export default async function Course({ params }) {
const courseData = await getCourse(params.slug);
return (
<div className="grid-cols-[minmax(200px,250px)_minmax(40ch,_1fr)] grid gap-4">
<aside className="shadow-md bg-white px-1">
<h1 className="text-2xl py-4 px-6 font-bold">Course nav</h1>
</aside>
<main className="prose w-full py-10 px-5 mx-auto">
<h1 className="text-3xl font-bold">{courseData.title}</h1>
{courseData.body && (
<RichText
content={courseData?.body?.json}
references={courseData?.body?.references}
/>
)}
</main>
</div>
);
}

Once those changes are in, we have the body for the course displayed, including any images you may have added.

"Not paid content" Course page with rich text displaying Now, let’s work on the course navigation.

Setting up the course navigation with modules and lessons

Since this will be on multiple pages, let’s abstract the navigation to its own component. Start by creating a new components directory inside the app directory. This will house our shared components.

Create a Navigation.jsx file with HTML from the course page.

export default function Navigation({}) {
return (
<aside className="shadow-md bg-white px-1">
<h1 className="text-2xl py-4 px-6 font-bold">Course nav</h1>
</aside>
);
}

Then replace the content on the page with the new component. Start by importing it at the top of the file.

import Navigation from "@/app/components/Navigation";

Then add it where the old nav was.

export default async function Course({ params }) {
const courseData = await getCourse(params.slug);
return (
<div className="grid-cols-[minmax(200px,250px)_minmax(40ch,_1fr)] grid gap-4">
**<Navigation />**
<main className="prose w-full py-10 px-5 mx-auto">
<h1 className="text-3xl font-bold">{courseData.title}</h1>
{/* ... the rest of the file */}
</main>
</div>
)
}

We need a few pieces of information from our data passed in. Let’s pass the course title and slug, the modules data, and the lessons data.

<Navigation title={courseData.title} slug={courseData.slug} modules={courseData?.modules} lessons={courseData?.lessons} />

Once that data is passed to our component, we can build out the navigation by looping through the module and lesson data. We can even check if the module is locked and add the lock icon as we did on the homepage.

export default function Navigation({ title, slug, lessonId, modules }) {
return (
<aside className="shadow-md bg-white px-1">
<h1 className="text-2xl py-4 px-6 font-bold">{title}</h1>
{modules &&
modules.map((module) => (
<div key={module.id} className="relative">
<h2 className="text-xl py-4 px-6 font-bold">{module.title}</h2>
<ul className="relative">
{module.lessons &&
module.lessons.map((lesson) => (
<li key={lesson.id} className="relative">
<a
className={`flex items-center text-sm py-4 px-6 h-12 overflow-hidden text-gray-700 text-ellipsis whitespace-nowrap rounded hover:text-gray-900 hover:bg-gray-100 transition duration-300 ease-in-out ${
lessonId === lesson.id ? "bg-gray-100" : ""
}`}
href={`/courses/${slug}/lessons/${lesson.id}`}
>
{lesson.title} {module.isLocked ? "🔒" : ""}
</a>
</li>
))}
</ul>
</div>
))}
</aside>
);
}

Now we have a full course page complete with navigation. Note in the code that we’re also checking for a matching lessonId. We haven’t passed that in yet, but this will be how we deal with active states in the navigation in the next section.

"Not paid content" course page with sidebar nested navigation showing two modules with the first module showing two lessons The navigation links are all broken, though. That’s because we haven’t created those dynamic routes yet.

Nesting lesson dynamic routes in the Next.js app router

To create the lesson routes, we want to nest them under the course. To nest the route in Next.js, the new directory should live under /app/courses/[slug] to nest this route under the appropriate course route. In this case, we'll name that directory lessons and put an [id] directory inside. This [id] directory is where we'll put the new page.jsx file that will create our page. This will pass an ID to our parameters allowing us to query Hygraph to fetch the appropriate lesson data.

This query is a bit more complicated. Not only do we need to get the lesson with a matching ID, but we also need deeper information about the course and module to build the navigation properly.

// /app/courses/[slug]/lessons/[id]/page.jsx
import Navigation from "@/app/components/Navigation";
import {RichText} from "@graphcms/rich-text-react-renderer";
async function getLesson(id) {
const response = await fetch(
"https://api-us-east-1-shared-usea1-02.hygraph.com/v2/clddka9yq1aw301ui7zzh4kf3/master",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query Lesson($id: ID!) {
lesson(where: {id: $id}) {
id
title
body {
json
}
moduleModel {
isLocked
}
description
title
videoUrl
navDetails: moduleModel {
title
course {
title
slug
modules: moduleModels {
id
isLocked
title
lessons {
id
title
}
}
}
lessons {
id
title
}
}
}
}
`,
variables: {
id,
},
}),
}
);
const json = await response.json();
return json.data.lesson;
}
export default async function Lesson({ params }) {
const {
id,
navDetails,
title,
body,
moduleModel,
loggedIn = true,
} = await getLesson(params.id);
return (
<div className="grid-cols-[minmax(200px,250px)_minmax(40ch,_1fr)] grid gap-4">
<Navigation
lessonId={id}
title={navDetails.course.title}
slug={navDetails.course.slug}
modules={navDetails.course.modules}
lessons={navDetails.lessons}
/>
<main className="prose w-full py-10 px-5 mx-auto">
<h1 className="text-3xl font-bold">{title}</h1>
<RichText
content={body?.json}
/>
</main>
</div>
);
}

At this point, we now have a fully functioning course and lesson structure.

"My Second Lesson" page showing rich text and a sidebar navigation with the correct lesson selected

#Next steps

So far in this series, we’ve created a content model for our learning platform and we’ve implemented the simple frontend in Next.js.

From here, put some more content in Hygraph, explore how it affects the frontend, and work on any styling you’d like to update.

In the next post in the series, we’ll work on locking the locked lessons and integrating with an authentication platform to check whether a user can access the content.

Blog Author

Bryan Robinson

Bryan Robinson

Head of Developer Relations

Bryan is Hygraph's Head of Developer Relations. He has a strong passion for developer education and experience as well as decoupled architectures, frontend development, and clean design.

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.