Skip to content

Commit b90b1ef

Browse files
authored
feat: add sanity cms graphql fragment colocation example (#1245)
### Description Adds a new Next.js 15 starter example showcasing Sanity CMS integration with GraphQL fragment colocation pattern. This example demonstrates modern best practices for type-safe GraphQL queries with server-side rendering. **Key Features:** - **GraphQL Fragment Colocation**: Fragments defined close to components for better maintainability - **Type Safety**: gql.tada for compile-time GraphQL type checking and IntelliSense - **ISR with On-Demand Revalidation**: Webhook-triggered cache invalidation using `revalidateTag()` for navigation/footer and `revalidatePath()` for content - **Server-Side Only GraphQL**: No client-side GraphQL bundle using URQL with React Server Components - **Comprehensive Sanity Schemas**: Posts, Pages, Navigation, Footer with full content management - **Modern Stack**: Next.js 15 App Router, React 19, Tailwind CSS v4, Biome formatter/linter **Documentation Included:** - `CLAUDE.md` - Agent/developer guidelines with commands, patterns, and best practices - `WEBHOOK_SETUP.md` - Step-by-step webhook configuration for on-demand revalidation - Complete setup instructions in README **Guide:** A comprehensive guide has been written for this example (currently in draft): https://vercel.com/guides/building-nextjs-apps-with-graphql-fragment-colocation-and-sanity-cms?__vercel_draft=1 ### Demo URL https://cms-graphql-fragments.vercel.app/ ### Type of Change - [x] New Example - [ ] Example updates (Bug fixes, new features, etc.) - [ ] Other (changes to the codebase, but not to examples)
1 parent ad82a9d commit b90b1ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+20813
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
SANITY_STUDIO_PROJECT_ID=<SANITY_STUDIO_PROJECT_ID>
2+
SANITY_STUDIO_DATASET=<SANITY_STUDIO_DATASET>
3+
SANITY_API_TOKEN=<SANITY_API_TOKEN>
4+
NEXT_PUBLIC_SANITY_GRAPHQL_URL=<NEXT_PUBLIC_SANITY_GRAPHQL_URL>
5+
6+
# Webhook secret for on-demand revalidation
7+
SANITY_REVALIDATE_SECRET=<SANITY_REVALIDATE_SECRET>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
!.env.example
36+
37+
# vercel
38+
.vercel
39+
40+
# typescript
41+
*.tsbuildinfo
42+
next-env.d.ts
43+
44+
# sanity
45+
.sanity
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Agent Guidelines for cms-graphql-fragments
2+
3+
## Commands
4+
- **Build**: `npm run build` (uses Next.js with Turbopack)
5+
- **Dev**: `npm run dev` (development server with Turbopack)
6+
- **Lint**: `npm run lint` (Biome linter)
7+
- **Format**: `npm run format` (Biome formatter)
8+
- **Sanity Studio**: `npm run sanity:dev` (start Sanity Studio development server)
9+
- **Deploy GraphQL**: `npm run sanity:deploy` (deploy GraphQL API to Sanity)
10+
- **Generate Types**: `npm run schema:generate` (generate GraphQL types from Sanity schema)
11+
- **No test command configured** - check with user if tests are needed
12+
13+
## Code Style
14+
- **Formatter**: Biome with 2-space indentation
15+
- **Imports**: Auto-organized by Biome, use `@/*` for src imports
16+
- **Types**: Strict TypeScript, explicit types for props/exports
17+
- **Naming**: camelCase for variables/functions, PascalCase for components
18+
- **Component Files**: Use kebab-case for component filenames (e.g., `my-component.tsx`)
19+
- **Components**: Function declarations with explicit return types when complex
20+
- **Error Handling**: Use TypeScript strict mode, handle async errors properly
21+
22+
### Biome Configuration
23+
- **Import Organization**: Automatically enabled on save/format
24+
- **Domain Rules**: Next.js and React recommended rules enabled
25+
- **Ignored Files**: `node_modules`, `.next`, `dist`, `build`, `.sanity`, `lib/generated`
26+
- **VCS Integration**: Git integration enabled with ignore file support
27+
28+
## Framework Specifics
29+
- Next.js 15 App Router with React 19
30+
- Tailwind CSS for styling
31+
- Use `next/image` for images, `next/font` for fonts
32+
- Export metadata objects for SEO
33+
- Use `className` prop for styling
34+
35+
## GraphQL & Data Fetching
36+
- **Client**: URQL with `@urql/next/rsc` for React Server Components
37+
- **Type Safety**: gql.tada with GraphQL introspection from `src/lib/generated/graphql-env`
38+
- **Client Setup**: Use `createGraphQLClient()` from `@/lib/graphql`
39+
- **Query Pattern**:
40+
```typescript
41+
import { registerUrql } from "@urql/next/rsc";
42+
import { createGraphQLClient, graphql } from "@/lib/graphql";
43+
44+
const { getClient } = registerUrql(createGraphQLClient);
45+
46+
const QUERY = graphql(`
47+
query QueryName($param: String!) {
48+
field
49+
}
50+
`);
51+
52+
const { data, error } = await getClient().query(QUERY, { param: "value" });
53+
```
54+
- **Error Handling**: Always check for `error` object and provide user-friendly error UI
55+
- **ISR**: Use `export const revalidate = 60` for Incremental Static Regeneration
56+
- **Scalars**: DateTime, Date (string), JSON (TypedObject from Sanity)
57+
58+
### Schema Generation Workflow
59+
1. **After Sanity schema changes**: Deploy GraphQL API with `npm run sanity:deploy`
60+
2. **Update types**: Run `npm run schema:generate` to regenerate TypeScript types
61+
3. **Generated files**: Types are created in `src/lib/generated/` (auto-ignored by Biome)
62+
4. **Import types**: Use generated types from `@/lib/graphql` for type safety
63+
64+
## GraphQL Fragment Colocation
65+
66+
### Fragment Definition Rules
67+
- **Server Components**: Define fragments directly in component files
68+
- **Client Components**: Define fragments in separate `.ts` files to avoid client/server boundary issues
69+
- **Fragment files**: Use `-fragment.ts` suffix (e.g., `nav-link-fragment.ts`)
70+
71+
### Client Component Pattern
72+
When a component needs `"use client"` directive:
73+
74+
1. **Fragment Definition** (`component-fragment.ts`):
75+
```typescript
76+
import { graphql } from "@/lib/graphql";
77+
78+
export const componentFragment = graphql(`
79+
fragment ComponentName on Type {
80+
field1
81+
field2
82+
}
83+
`);
84+
```
85+
86+
2. **Client Component** (`component.tsx`):
87+
```typescript
88+
"use client";
89+
90+
import { type FragmentOf } from "@/lib/graphql";
91+
import type { componentFragment } from "./component-fragment";
92+
93+
interface Props {
94+
data: FragmentOf<typeof componentFragment>;
95+
}
96+
97+
export function Component({ data }: Props) {
98+
// Client-side logic (hooks, event handlers, etc.)
99+
}
100+
```
101+
102+
3. **Parent Server Component**:
103+
```typescript
104+
import { componentFragment } from "./component-fragment";
105+
import { Component } from "./component";
106+
107+
const parentFragment = graphql(`...`, [componentFragment]);
108+
```
109+
110+
### Why This Pattern?
111+
- **Prevents Runtime Errors**: Avoids "f.definitions is not iterable" errors
112+
- **Maintains Colocation**: Fragments stay close to components that use them
113+
- **Respects Boundaries**: Separates server-side GraphQL logic from client-side interactivity
114+
- **Type Safety**: Components get proper TypeScript types via `FragmentOf<typeof {GraphQL Fragment}>`
115+
116+
## Sanity CMS Integration
117+
118+
### Available Schema Types
119+
The project includes comprehensive Sanity schemas defined in `src/lib/schema.ts`:
120+
121+
- **Post**: Blog posts with title, slug, excerpt, content, publishedAt
122+
- **Page**: Static pages with title, slug, excerpt, content, SEO settings, published status
123+
- **Navigation**: Site navigation with title and navigation items (label, href, isExternal)
124+
- **Footer**: Footer configuration with title, description, links, social links, copyright
125+
- **SEO**: SEO settings object with metaTitle and metaDescription
126+
- **Social Links**: Platform-specific social media links (Twitter, GitHub, LinkedIn, etc.)
127+
128+
### Content Management Workflow
129+
1. **Development**: Use `npm run sanity:dev` to start Sanity Studio
130+
2. **Schema Changes**: After modifying `src/lib/schema.ts`, deploy with `npm run sanity:deploy`
131+
3. **Type Generation**: Run `npm run schema:generate` to update GraphQL types
132+
4. **Content Creation**: Use Sanity Studio to create and manage content
133+
5. **ISR**: Content changes trigger revalidation automatically (only available on deployed project)

0 commit comments

Comments
 (0)