Socket
Book a DemoInstallSign in
Socket

@spoolcms/nextjs

Package Overview
Dependencies
Maintainers
1
Versions
113
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@spoolcms/nextjs

The beautiful headless CMS for Next.js developers

3.0.8
latest
Source
npmnpm
Version published
Weekly downloads
290
468.63%
Maintainers
1
Weekly downloads
 
Created
Source

@spoolcms/nextjs Integration Guide

This guide provides all the necessary steps and code examples to integrate Spool CMS into a Next.js application.

Core Features:

  • Headless CMS: Manage your content in the Spool admin dashboard.
  • Real-time API: Fetch content instantly in your Next.js app.
  • Simple Setup: Get started with a single CLI command.

1. Installation & Setup

First, install the Spool Next.js package and run the setup command from the root of your Next.js project.

# 1. Install the package
npm install @spoolcms/nextjs

# 2. Run the setup command to create your API route
npx create-spool-route

This command automatically creates the file app/api/spool/[...route]/route.ts (or pages/api/spool/[...route].ts for Pages Router) for you.

2. Environment Variables

Next, add your Spool credentials to your local environment file (.env.local). You can find these keys in your Spool project settings.

# .env.local

SPOOL_API_KEY="your_spool_api_key"
SPOOL_SITE_ID="your_spool_site_id"

Note: Be sure to copy the entire API key from your Spool dashboard, including the spool_ prefix.

3. Core Concepts & API Helpers

The @spoolcms/nextjs package provides simple helpers to fetch your content.

To avoid repeating your API key and Site ID, create a shared config file.

lib/spool.ts

import { SpoolConfig } from '@spoolcms/nextjs/types';

export const spoolConfig: SpoolConfig = {
  apiKey: process.env.SPOOL_API_KEY!,
  siteId: process.env.SPOOL_SITE_ID!,
};

Fetching a List of Content (getSpoolContent)

To get all items from a collection, call getSpoolContent with just the collection's slug.

import { getSpoolContent } from '@spoolcms/nextjs';
import { spoolConfig } from '@/lib/spool';

// Returns an array of all items in the 'blog' collection
const posts = await getSpoolContent(spoolConfig, 'blog');

Fetching a Single Content Item (getSpoolContent)

To get a single item by its slug, provide the slug as the third argument.

import { getSpoolContent } from '@spoolcms/nextjs';
import { spoolConfig } from '@/lib/spool';

// Returns the single post with the matching slug
const post = await getSpoolContent(spoolConfig, 'blog', 'my-first-post');

Fetching Collection Schemas (getSpoolCollections)

If you need the schema or metadata for your collections, use getSpoolCollections.

import { getSpoolCollections } from '@spoolcms/nextjs';
import { spoolConfig } from '@/lib/spool';

// Returns an array of all collection objects (id, name, slug, schema)
const collections = await getSpoolCollections(spoolConfig);

4. Handling Images & Thumbnails

Spool automatically generates multiple sizes for every uploaded image:

LabelWidthFormat
thumb160 pxwebp
small480 pxwebp
medium960 pxwebp
large1600 pxwebp
originaloriginal mime

Image fields now return either a plain URL string (legacy items) or an object:

{
  "original": "https://media…/foo.jpg",
  "thumb": "https://media…/foo_thumb.webp",
  "small": "https://media…/foo_small.webp",
  "medium": "https://media…/foo_medium.webp",
  "large": "https://media…/foo_large.webp"
}

To make this seamless in Next.js you can import the helper exported by @spoolcms/nextjs:

import { img } from '@spoolcms/nextjs';

// Use thumbnail for fast loading in lists
<Image src={img(item.headerImage, 'thumb')} width={160} height={90} />

// Use small for medium-sized displays
<Image src={img(item.headerImage, 'small')} width={480} height={270} />

// Use medium for large content blocks
<Image src={img(item.headerImage, 'medium')} width={960} height={540} />

// Use large for hero sections
<Image src={img(item.headerImage, 'large')} width={1600} height={900} />

// Use original for full-size displays
<Image src={img(item.headerImage, 'original')} width={1200} height={675} />

Additional image behavior:

  • Never upscales: if the original image is smaller than a requested size, img(value, 'large') (or any size) returns the original URL.
  • Backfill gracefully adds medium/large where possible; missing sizes are served via original.

Additional utilities:

import { getImageSizes, hasMultipleSizes } from '@spoolcms/nextjs';

// Get all available sizes
const sizes = getImageSizes(item.headerImage);
// Returns: { original: "...", thumb: "...", small: "..." } or null

// Check if image has multiple sizes (vs legacy single URL)
const hasMultiple = hasMultipleSizes(item.headerImage);
// Returns: true for new images, false for legacy single URLs

• Pass the image field value (string or object) and the desired size ('thumb' | 'small' | 'original'). • Falls back gracefully so existing content keeps working. • The admin interface automatically uses thumbnails for fast table rendering.

5. Default Fields in Every Collection

Every new collection you create in Spool automatically includes a set of foundational fields so you always have sensible SEO metadata and publication info without any extra configuration.

FieldLocationTypeNotes
descriptionitem.datastringOptional short summary (used in lists & default meta description)
seoTitleitem.datastringOptional. Overrides title for search engines
seoDescriptionitem.datastringOptional. Overrides description for search engines
ogTitleitem.datastringOptional. Title for social sharing (Open Graph)
ogDescriptionitem.datastringOptional. Description for social sharing (Open Graph)
ogImageitem.dataimage URL*Optional. Social preview / hero image
titletop-levelstringRequired. Main headline for the item
slugtop-levelstringRequired. URL-friendly identifier set when creating the item
statustop-leveldraft | publishedDefaults to draft. Controls visibility
published_attop-leveldatetimeAutomatic. Set the first time status becomes published
updated_attop-leveldatetimeAutomatic. Updated every time you modify the item
  • ogImage is returned as a full URL string when you request content.

You can add any custom fields you like on top of these defaults.

5. Handling Markdown Content

Spool makes working with markdown incredibly intuitive! When you have a markdown field in your collection, Spool creates smart field objects that default to HTML but provide access to raw markdown when needed.

Smart Markdown Fields

When you request HTML processing with { renderHtml: true }, markdown fields become smart objects:

const post = await getSpoolContent(spoolConfig, 'blog', 'my-post', { renderHtml: true });

// ✅ Default behavior: HTML (perfect for rendering)
<div dangerouslySetInnerHTML={{ __html: post.body }} />

// ✅ Explicit HTML access
<div dangerouslySetInnerHTML={{ __html: post.body.html }} />

// ✅ Raw markdown access (when you need it)
const rawMarkdown = post.body.markdown;

Before vs After

Before (complex):

// Had to remember the _html suffix
<div dangerouslySetInnerHTML={{ __html: post.body_html }} />
const rawMarkdown = post.body; // Different field for raw markdown

After (intuitive):

// Just use the field directly - defaults to HTML!
<div dangerouslySetInnerHTML={{ __html: post.body }} />
const rawMarkdown = post.body.markdown; // Easy access to raw markdown

6. Webhook Integration

Spool provides secure webhook utilities for real-time content updates in your Next.js application.

Basic Webhook Handler

// app/api/webhooks/spool/route.ts
import { createSpoolWebhookHandler } from '@spoolcms/nextjs';
import { revalidatePath } from 'next/cache';

const handleWebhook = createSpoolWebhookHandler({
  secret: process.env.SPOOL_WEBHOOK_SECRET, // Optional but recommended
  onWebhook: async (data, headers) => {
    console.log(`Processing ${data.event} for ${data.collection}/${data.slug}`);
    
    // Revalidate paths based on collection
    if (data.collection === 'blog') {
      revalidatePath('/blog');
      if (data.slug) revalidatePath(`/blog/${data.slug}`);
    }
    
    revalidatePath('/'); // Always revalidate homepage
  }
});

export const POST = handleWebhook;

Manual Webhook Processing

For more control, use the individual utilities:

// app/api/webhooks/spool/route.ts
import { 
  verifySpoolWebhook, 
  parseSpoolWebhook, 
  getSpoolWebhookHeaders 
} from '@spoolcms/nextjs';
import { revalidatePath } from 'next/cache';

export async function POST(request: Request) {
  const payload = await request.text();
  const headers = getSpoolWebhookHeaders(request);
  const secret = process.env.SPOOL_WEBHOOK_SECRET;
  
  // Verify signature (recommended for security)
  if (secret && headers.signature) {
    const isValid = verifySpoolWebhook(payload, headers.signature, secret);
    if (!isValid) {
      return new Response('Unauthorized', { status: 401 });
    }
  }
  
  // Parse and validate payload
  const data = parseSpoolWebhook(payload);
  if (!data) {
    return new Response('Invalid payload', { status: 400 });
  }
  
  // Process webhook
  console.log(`Processing ${data.event} for ${data.collection}`);
  
  // Revalidate relevant paths
  revalidatePath('/');
  if (data.collection === 'blog') {
    revalidatePath('/blog');
    if (data.slug) revalidatePath(`/blog/${data.slug}`);
  }
  
  return new Response('OK');
}

Webhook Types

import type { SpoolWebhookPayload } from '@spoolcms/nextjs';

// Webhook payload structure
interface SpoolWebhookPayload {
  event: 'content.created' | 'content.updated' | 'content.published' | 'content.deleted';
  site_id: string;
  collection: string;
  slug?: string;
  item_id: string;
  timestamp: string;
}

Environment Variables

Add your webhook secret to your environment file:

# .env.local
SPOOL_WEBHOOK_SECRET="your_webhook_secret_from_spool_admin"

7. Example: Building a Blog

Here is a complete example for creating a blog list and detail pages.

Important Setup Notes:

  • Dynamic Routes Required: To enable individual post URLs like /blog/your-post-slug you must create a dynamic route folder app/blog/[slug] with its own page.tsx. Without this folder, Next.js will return a 404 even though the content exists in Spool.
  • Add Content First: Before testing your frontend, make sure to add some content in your Spool admin dashboard and fill in at least the title field. Empty titles will cause your blog listing to appear broken.

Blog Listing Page

This page fetches all posts from the "blog" collection and displays them in a grid.

app/blog/page.tsx

import { getSpoolContent } from '@spoolcms/nextjs';

Keywords

nextjs

FAQs

Package last updated on 18 Aug 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.