This project uses Prismic CMS for content management alongside a Next.js frontend.
This project follows Conventional Commits specification for commit messages. All commits should use the format:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Common types include:
feat:- new featuresfix:- bug fixesdocs:- documentation updatesstyle:- formatting changesrefactor:- code refactoringtest:- adding testschore:- maintenance tasks
This project uses Prismic CMS for headless content management, providing a powerful and flexible backend for managing website content.
- Prismic Repository: Cloud-based content management at
world-sevens-football.prismic.io - Frontend: Next.js application that fetches content from Prismic API
- Content Delivery: Real-time content updates via Prismic's CDN
- Slice Machine: Local development tool for managing content types and slices
- Custom Types: Define flexible content models for different content types
- Slice Machine: Local development tool for creating and managing slices
- Rich Text: Advanced rich text editing with embedded content
- Preview System: Full-website previews of draft content before publishing
- Image Optimization: Automatic image transformations and CDN delivery
- Releases: Group changes across multiple documents for coordinated publishing
- Schema Definition: Define Custom Types and Slices using Slice Machine at
http://localhost:9999 - Content Creation: Use Prismic dashboard to create and manage content
- Frontend Integration: Query content using Prismic SDK in Next.js components
- Preview & Test: Use preview functionality to see changes before publishing
- Blog: News articles and blog posts with rich text content
- Tournament: Tournament information with navigation settings and images
- Policy: Legal documents with rich text content and optional PDF files
- Team Member: Staff profiles with departments and display ordering
- Website: Site-wide navigation and footer configuration
- Image With Text: Content blocks combining images with rich text descriptions
- Prismic Documentation - Complete guide to Prismic CMS
- Next.js Integration - Next.js specific documentation
- Slice Machine Documentation - Content modeling tool
- Preview System - Draft content preview functionality
This project uses the official Prismic approach for TypeScript types:
- Types File:
prismicio-types.d.ts(auto-generated by Slice Machine) - Generator:
@slicemachine/adapter-next - Standard: Follows Prismic Next.js starter
import type { BlogDocument, PolicyDocument, TournamentDocument } from "../../../prismicio-types"- Official Standard: Recommended by Prismic for Next.js projects
- Slice Machine Integration: Automatically generates types when you modify content models
- No Conflicts: Single source of truth for all Prismic types
- Always Current: Types update automatically when content models change
Prismic provides automatic image optimization through their CDN. Use the image field directly:
import { PrismicNextImage } from "@prismicio/next"
// In your component
<PrismicNextImage
field={document.data.image}
width={800}
height={600}
className="rounded-lg"
/>This automatically provides responsive images, format optimization (WebP/AVIF), and CDN delivery.
All components in this project follow consistent patterns for maintainability and type safety:
- Import React and utilities at the top
- Use
React.forwardReffor proper ref forwarding - Accept
React.ComponentProps<"element">for full HTML element support - Destructure
classNameand spread remaining props
- If a component has no additional props: Declare props type inline, don't create a separate type/interface
- If a component has additional props: Create a separate interface that extends the base props
- Use
React.ComponentProps<"element">orReact.HTMLAttributes<HTMLElement>as base types
- Use the
cnutility from@/lib/utilsfor class merging - Always accept and merge
classNameprop with base styles
Component with no additional props (inline declaration):
import * as React from "react"
import { cn } from "@/lib/utils"
const MyComponent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
className={cn("base-styles", className)}
{...props}
/>
)
})
MyComponent.displayName = "MyComponent"Component with additional props (separate interface):
import * as React from "react"
import { cn } from "@/lib/utils"
interface MyComponentProps extends React.ComponentProps<"div"> {
variant?: "primary" | "secondary"
size?: "sm" | "md" | "lg"
}
const MyComponent = React.forwardRef<HTMLDivElement, MyComponentProps>(
({ className, variant = "primary", size = "md", ...props }, ref) => {
return (
<div
ref={ref}
className={cn("base-styles", variant, size, className)}
{...props}
/>
)
}
)
MyComponent.displayName = "MyComponent"- Use named exports in curly braces
- Set
displayNamefor better debugging
This project uses Prismic CMS with Slice Machine for flexible content management. Follow these conventions when implementing new content types and components:
┌─────────────────────────┐ ┌─────────────────────┐
│ Prismic Dashboard │ │ Next.js App │
│ │ │ │
│ - Content editing │────▶│ - Server components│
│ - Media management │ │ - Static generation│
│ - Preview mode │ │ - API integration │
└─────────────────────────┘ └─────────────────────┘
│ │
└──────────┬───────────────┘
│
┌──────▼──────┐
│ Slice Machine│
│ - Local dev │
│ - Type gen │
└─────────────┘
When creating Custom Types in Slice Machine:
{
"Main": {
"title": {
"type": "Text",
"config": {
"label": "Title",
"placeholder": "Enter title"
}
},
"description": {
"type": "RichText",
"config": {
"label": "Description",
"placeholder": "Enter description",
"allowTargetBlank": true,
"multi": "paragraph,preformatted,heading1,heading2,heading3,heading4,heading5,heading6,strong,em,hyperlink,image,embed,list-item,o-list-item,rtl"
}
},
"image": {
"type": "Image",
"config": {
"label": "Image",
"constraint": {
"width": 1200,
"height": 800
},
"thumbnails": []
}
}
}
}Key Requirements:
- Use appropriate field types for content structure
- Configure constraints for images and text fields
- Set up proper labels and placeholders for content editors
- Use RichText for formatted content, Text for simple strings
Follow this pattern for Prismic content components:
// app/[...]/page.tsx or components/[feature]/[name].tsx
import { createClient } from "@/prismicio"
import type { BlogDocument } from "../../../prismicio-types"
export async function BlogPage({ params }: { params: { uid: string } }) {
const client = createClient()
const blog = await client.getByUID("blog", params.uid)
if (!blog) {
notFound()
}
return (
<Container>
<h1>{blog.data.title}</h1>
<PrismicRichText field={blog.data.content} />
{blog.data.image && (
<PrismicNextImage field={blog.data.image} />
)}
</Container>
)
}// cms/queries/[content-type].ts
import { createClient } from "../../prismicio"
import type { BlogDocument } from "../../../prismicio-types"
import * as prismic from "@prismicio/client"
/**
* Get a single blog by UID (slug)
*/
export async function getBlogBySlug(uid: string): Promise<BlogDocument | null> {
try {
const client = createClient()
return await client.getByUID("blog", uid)
} catch (error) {
if (error instanceof Error && "status" in error && (error as { status: number }).status === 404) {
return null
}
throw error
}
}
/**
* Get all blogs ordered by date
*/
export async function getAllBlogs(): Promise<BlogDocument[]> {
try {
const client = createClient()
return await client.getAllByType("blog", {
orderings: [
{ field: "my.blog.date", direction: "desc" },
{ field: "my.blog.title", direction: "asc" },
],
})
} catch (error) {
if (error instanceof Error && error.message.includes("No documents were returned")) {
return []
}
throw error
}
}Use PrismicRichText component for formatted content:
import { PrismicRichText } from "@prismicio/react"
import { H1, H2, H3, P } from "@/components/website-base/typography"
const components = {
heading1: ({ children }) => <H1>{children}</H1>,
heading2: ({ children }) => <H2>{children}</H2>,
heading3: ({ children }) => <H3>{children}</H3>,
paragraph: ({ children }) => <P>{children}</P>,
hyperlink: ({ node, children }) => (
<a
href={node.data.url || ""}
className="underline underline-offset-2"
{...(node.data.link_type === "Web" ? { target: "_blank", rel: "noopener noreferrer" } : {})}
>
{children}
</a>
),
}
// In your component
<PrismicRichText
field={document.data.content}
components={components}
/>Implement robust error handling for API calls:
// cms/queries/[content-type].ts
export async function getContentWithFallback(): Promise<ContentDocument[]> {
try {
const client = createClient()
return await client.getAllByType("content", {
orderings: [{ field: "my.content.date", direction: "desc" }],
})
} catch (error) {
console.error("Error fetching content:", error)
// Return empty array for graceful degradation
return []
}
}
// For build resilience, use Promise.allSettled
export async function getMultipleContentTypes() {
const [blogs, tournaments, policies] = await Promise.allSettled([
getAllBlogs(),
getTournaments(),
getPolicies()
]).then(results => results.map(result =>
result.status === 'fulfilled' ? result.value : []
))
return { blogs, tournaments, policies }
}Configure Slice Machine for local development:
// slicemachine.config.json
{
"apiEndpoint": "https://your-repo-name.cdn.prismic.io/api/v2",
"repositoryName": "your-repo-name",
"adapter": "@slicemachine/adapter-next",
"libraries": ["./src/cms/slices"],
"localSliceSimulatorURL": "http://localhost:3000/slice-simulator"
}Start Slice Machine for content modeling:
npm run slicemachine- Server Components: Use server components for data fetching and static content
- Type Safety: Always use generated Prismic types from
prismicio-types.d.ts - Error Handling: Implement graceful fallbacks for API failures
- Image Optimization: Use
PrismicNextImagefor automatic optimization - Rich Text: Use
PrismicRichTextwith custom components for consistent styling - Caching: Leverage Next.js caching with appropriate revalidation strategies
- Preview Mode: Access draft content via Prismic's preview system
- Slice Simulator: Test slices in isolation at
/slice-simulator - Local Development:
npm run dev # Start Next.js npm run slicemachine # Start Slice Machine (separate terminal)
Pattern 1: Document by UID
const document = await client.getByUID("blog", params.slug)Pattern 2: All Documents of Type
const documents = await client.getAllByType("blog", {
orderings: [{ field: "my.blog.date", direction: "desc" }]
})Pattern 3: Filtered Documents
const documents = await client.getAllByType("team_member", {
filters: [prismic.filter.at("my.team_member.department", "Leadership")]
})