Hygraph
Build with Hygraph (formerly GraphCMS) as a GraphQL-native headless CMS. Use this skill when the project needs a CMS with a native GraphQL API, content federation, localization, scheduled publishing, asset management, or integration with Next.js, Nuxt, or Astro. Covers schema modeling, querying with GraphQL, mutations, webhooks, and caching strategies.
You are an expert in integrating Hygraph as a headless CMS. Hygraph is a GraphQL-native headless CMS that provides a fully auto-generated GraphQL API from your content schema. It supports content federation, allowing you to combine content from Hygraph with data from external systems in a single ## Key Points - **Post** model: title (String), slug (String, unique), excerpt (String), - **Author** model: name (String), bio (String), photo (Asset) - **Category** model: name (String), slug (String, unique) - Use the `stage: PUBLISHED` argument in queries for production and omit it (or use `DRAFT`) only behind preview mode - Apply image transformations via the `url(transformation: ...)` argument in queries instead of client-side resizing - Use Hygraph's built-in pagination (`first`, `skip`, and the `Connection` aggregate) instead of fetching all records at once - Leverage content federation to join Hygraph content with external APIs (e.g., product data from Shopify) in a single schema - Wrap `graphql-request` calls with Next.js `fetch` and `next.tags` for granular on-demand revalidation - Exposing the permanent auth token on the client side — use a public Content API with read-only permissions for client-side fetching, or fetch exclusively on the server - Querying `DRAFT` stage content in production — draft content is only visible to authenticated requests and should be gated behind preview mode - Not handling Rich Text `json` output properly — use `@graphcms/rich-text-react-renderer` to render the AST; falling back to `html` loses embed and asset rendering - Ignoring rate limits on the Content API — Hygraph enforces rate limits per second; use caching and ISR to reduce direct API calls ## Quick Example ```bash npm install graphql graphql-request # Or with urql / Apollo npm install @urql/core graphql npm install @apollo/client graphql ``` ```env HYGRAPH_ENDPOINT=https://your-region.hygraph.com/v2/your-project-id/master HYGRAPH_TOKEN=your-permanent-auth-token HYGRAPH_WEBHOOK_SECRET=your-webhook-secret HYGRAPH_PREVIEW_TOKEN=your-draft-token ```
skilldb get cms-services-skills/hygraphFull skill: 387 linesHygraph — CMS Integration
You are an expert in integrating Hygraph as a headless CMS. Hygraph is a GraphQL-native headless CMS that provides a fully auto-generated GraphQL API from your content schema. It supports content federation, allowing you to combine content from Hygraph with data from external systems in a single GraphQL query.
Core Philosophy
Overview
Hygraph exposes three API endpoints: a high-performance Content API (read-only, CDN-backed), a Content API with draft stages, and a Management API for programmatic schema and content changes. All APIs are GraphQL. Content is organized into models, and each model auto-generates query, mutation, and subscription types.
Setup & Configuration
Install
npm install graphql graphql-request
# Or with urql / Apollo
npm install @urql/core graphql
npm install @apollo/client graphql
Environment variables
HYGRAPH_ENDPOINT=https://your-region.hygraph.com/v2/your-project-id/master
HYGRAPH_TOKEN=your-permanent-auth-token
HYGRAPH_WEBHOOK_SECRET=your-webhook-secret
HYGRAPH_PREVIEW_TOKEN=your-draft-token
Client configuration
// lib/hygraph.ts
import { GraphQLClient } from 'graphql-request';
export const hygraph = new GraphQLClient(process.env.HYGRAPH_ENDPOINT!, {
headers: {
Authorization: `Bearer ${process.env.HYGRAPH_TOKEN}`,
},
});
// Preview client for draft content
export const hygraphPreview = new GraphQLClient(process.env.HYGRAPH_ENDPOINT!, {
headers: {
Authorization: `Bearer ${process.env.HYGRAPH_PREVIEW_TOKEN}`,
},
});
Core Patterns
Content modeling
Define models in the Hygraph UI. A typical blog schema:
- Post model: title (String), slug (String, unique), excerpt (String), content (Rich Text), coverImage (Asset), author (relation to Author), categories (relation to Category, many), publishedAt (DateTime)
- Author model: name (String), bio (String), photo (Asset)
- Category model: name (String), slug (String, unique)
Querying content
// lib/hygraph.queries.ts
import { gql } from 'graphql-request';
export const GET_POSTS = gql`
query GetPosts($first: Int = 20, $skip: Int = 0) {
posts(first: $first, skip: $skip, orderBy: publishedAt_DESC, stage: PUBLISHED) {
id
title
slug
excerpt
publishedAt
coverImage {
url(transformation: { image: { resize: { width: 800, fit: crop } } })
width
height
}
author {
name
photo {
url(transformation: { image: { resize: { width: 64, height: 64, fit: crop } } })
}
}
categories {
name
slug
}
}
postsConnection {
aggregate {
count
}
}
}
`;
export const GET_POST_BY_SLUG = gql`
query GetPostBySlug($slug: String!) {
post(where: { slug: $slug }, stage: PUBLISHED) {
id
title
slug
content {
html
json
}
publishedAt
coverImage {
url
width
height
}
author {
name
bio
photo {
url
}
}
categories {
name
slug
}
seo {
title
description
image {
url
}
}
}
}
`;
Data fetching in Next.js App Router
// app/blog/page.tsx
import { hygraph } from '@/lib/hygraph';
import { GET_POSTS } from '@/lib/hygraph.queries';
import Link from 'next/link';
import Image from 'next/image';
interface PostsResponse {
posts: Array<{
id: string;
title: string;
slug: string;
excerpt: string;
coverImage: { url: string; width: number; height: number };
}>;
}
export default async function BlogPage() {
const { posts } = await hygraph.request<PostsResponse>(GET_POSTS, {
first: 20,
});
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>
<Image
src={post.coverImage.url}
alt={post.title}
width={800}
height={450}
/>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</Link>
</li>
))}
</ul>
);
}
Rich Text rendering
// components/RichTextRenderer.tsx
import { RichText } from '@graphcms/rich-text-react-renderer';
import type { RichTextContent } from '@graphcms/rich-text-types';
import Image from 'next/image';
interface Props {
content: RichTextContent;
}
export function RichTextRenderer({ content }: Props) {
return (
<RichText
content={content}
renderers={{
h1: ({ children }) => <h1 className="text-4xl font-bold">{children}</h1>,
h2: ({ children }) => <h2 className="text-3xl font-semibold">{children}</h2>,
p: ({ children }) => <p className="leading-relaxed">{children}</p>,
a: ({ children, href, openInNewTab }) => (
<a
href={href}
target={openInNewTab ? '_blank' : '_self'}
rel={openInNewTab ? 'noopener noreferrer' : undefined}
>
{children}
</a>
),
img: ({ src, altText, width, height }) => (
<Image
src={src!}
alt={altText || ''}
width={width || 800}
height={height || 450}
/>
),
code_block: ({ children }) => (
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
<code>{children}</code>
</pre>
),
embed: {
Post: ({ nodeId, title, slug }) => (
<a href={`/blog/${slug}`} className="embedded-post">
{title}
</a>
),
},
}}
/>
);
}
Localization
// Fetching localized content
export const GET_LOCALIZED_PAGE = gql`
query GetPage($slug: String!, $locale: Locale!) {
page(where: { slug: $slug }, locales: [$locale, en]) {
title
content {
html
}
localizations(includeCurrent: false) {
locale
}
}
}
`;
// Usage
const page = await hygraph.request(GET_LOCALIZED_PAGE, {
slug: 'about',
locale: 'de',
});
Webhook-triggered revalidation
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import crypto from 'crypto';
interface HygraphWebhookPayload {
operation: 'publish' | 'unpublish' | 'update' | 'delete';
data: {
__typename: string;
slug?: string;
id: string;
stage: string;
};
}
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get('gcms-signature');
const expectedSig = crypto
.createHmac('sha256', process.env.HYGRAPH_WEBHOOK_SECRET!)
.update(rawBody)
.digest('base64');
if (signature !== expectedSig) {
return new Response('Invalid signature', { status: 401 });
}
const payload: HygraphWebhookPayload = JSON.parse(rawBody);
const typeName = payload.data.__typename.toLowerCase();
revalidateTag(typeName);
if (payload.data.slug) {
revalidateTag(`${typeName}:${payload.data.slug}`);
}
return Response.json({
revalidated: true,
type: typeName,
now: Date.now(),
});
}
Caching with Next.js fetch tags
// lib/hygraph.cached.ts
import { hygraph } from './hygraph';
export async function fetchHygraph<T>(
query: string,
variables?: Record<string, any>,
tags?: string[]
): Promise<T> {
// graphql-request uses fetch under the hood;
// for cache tags, use native fetch with the Hygraph endpoint.
const res = await fetch(process.env.HYGRAPH_ENDPOINT!, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.HYGRAPH_TOKEN}`,
},
body: JSON.stringify({ query, variables }),
next: { tags: tags ?? ['hygraph'] },
});
const json = await res.json();
if (json.errors) {
throw new Error(json.errors.map((e: any) => e.message).join('\n'));
}
return json.data;
}
Best Practices
- Use the
stage: PUBLISHEDargument in queries for production and omit it (or useDRAFT) only behind preview mode - Apply image transformations via the
url(transformation: ...)argument in queries instead of client-side resizing - Use Hygraph's built-in pagination (
first,skip, and theConnectionaggregate) instead of fetching all records at once - Leverage content federation to join Hygraph content with external APIs (e.g., product data from Shopify) in a single schema
- Wrap
graphql-requestcalls with Next.jsfetchandnext.tagsfor granular on-demand revalidation
Common Pitfalls
- Exposing the permanent auth token on the client side — use a public Content API with read-only permissions for client-side fetching, or fetch exclusively on the server
- Querying
DRAFTstage content in production — draft content is only visible to authenticated requests and should be gated behind preview mode - Not handling Rich Text
jsonoutput properly — use@graphcms/rich-text-react-rendererto render the AST; falling back tohtmlloses embed and asset rendering - Ignoring rate limits on the Content API — Hygraph enforces rate limits per second; use caching and ISR to reduce direct API calls
Anti-Patterns
Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.
Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.
Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.
Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.
Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.
Install this skill directly: skilldb add cms-services-skills
Related Skills
Keystatic
Build with Keystatic for git-based content management. Use this skill when the project needs a CMS that stores content in markdown, MDX, JSON, or YAML files committed to Git. Covers local mode, GitHub mode, content collections, singletons, component blocks, and integration with Next.js and Astro.
Notion As Cms
Leverage Notion as a flexible, collaborative content management system for your web applications. Use this skill when you need a user-friendly editor, rich content types, and programmatic access to structured data stored in Notion databases and pages.
Payload
Build with Payload CMS for code-first TypeScript content management. Use this skill when the project needs Payload integration for collections, fields, hooks, access control, local API, admin panel, or Next.js native embedding. Covers Payload 3.x with its Next.js-native architecture.
Prismic
Prismic is a headless CMS focused on content modeling and multi-channel delivery. Use this skill when projects require flexible content structures, rich text editing, internationalization, and a robust GraphQL/REST API for publishing digital experiences across various front-ends.
Sanity
Build with Sanity CMS for structured content management. Use this skill when the project needs a headless CMS with real-time collaboration, GROQ queries, Portable Text rendering, image pipelines, or Next.js integration. Covers schema definitions, content modeling, Studio customization, and webhooks.
Storyblok
Build with Storyblok as a visual headless CMS. Use this skill when the project needs a component-based CMS with a real-time visual editor, nested content blocks, localization, asset management, or framework integration (Next.js, Nuxt, Astro). Covers space setup, content modeling with bloks, the Storyblok Bridge, and webhook-driven revalidation.