Wisp CMS Next.js Blog Starter Template

repository·master·Indexed 41 days ago

https://github.com/wisp-cms/nextjs-blog-cms-wisp

A feature-rich blogging starter template built with Next.js 15 (App Router), React Server Components, Tailwind CSS, and Shadcn UI, using Wisp CMS as the backend. Includes built-in support for server-side rendering, AI-powered related post suggestions, automatic sitemap and Open Graph image generation, and a secure HTML rendering system via the PostContent component.

Tokens
4K
Snippets
14
Records
18
Agent score
51%

What's inside Wisp CMS Next.js Blog

  1. Overview of Next.js Blog CMS features

    master

    This template is a feature-rich blogging starter kit built with Next.js 15, Tailwind CSS, and Shadcn UI. Key features include:

    • Server-Side Rendering: Uses Next.js 15 React Server Components.
    • Content Management: Powered by Wisp CMS for an intuitive editorial experience.
    • UI/UX: Responsive layout, light & dark mode support, and tag-based post filtering.
    • SEO & Discovery: Automatic hierarchical sitemap generation, automatic Open Graph image generation, and an RSS Feed.
    • AI Features: Automatic related posts suggestions using AI content recommendation.
  2. Quick Start Guide for Next.js Blog CMS

    master

    To run this blog template locally, follow these steps:

    1. Install dependencies: Use the --legacy-peer-deps flag because next-themes is not yet upgraded to React 19.
    2. Configure environment variables: Copy .env.example to .env and populate the NEXT_PUBLIC_BLOG_ID variable with your Blog ID from the Wisp CMS setup page.
    3. Start development server: Run the development command to view the blog at http://localhost:3000.

    Note for Vercel deployment: Ensure your Vercel install command is also set to npm i --legacy-peer-deps to avoid installation errors.

    # 1. Install dependencies
    npm i --legacy-peer-deps
    
    # 2. Setup environment variables
    cp .env.example .env
    
    # 3. Run development server
    npm run dev
  3. Customize the site header and navigation links

    master

    The site's header and navigation are controlled by the Header and Navigation components in src/components/Header.tsx.

    To change the navigation links (e.g., adding a 'Contact' page), you must modify the menuItems array within this file. Each item follows the MenuItem interface:

    • name: The display text for the link.
    • href: The URL path.
    • openInNewTab (optional): If true, the link will open in a new tab using target="_blank".

    The Header component also dynamically pulls the site name from your project configuration via config.blog.name.

    interface MenuItem {
      name: string;
      href: string;
      openInNewTab?: boolean;
    }
    
    const menuItems: MenuItem[] = [
      { name: "Blog", href: "/" },
      { name: "About", href: "/about" },
      // Add custom links here
    ];
  4. Configure environment variables for the Wisp Blog

    master

    The application configuration is driven by environment variables. You must provide NEXT_PUBLIC_BLOG_ID for the application to function. Other variables allow you to customize the blog's identity, SEO metadata, and Open Graph image security. If not provided, the application uses fallback values.

    # Required
    NEXT_PUBLIC_BLOG_ID=your_blog_id
    
    # Optional
    NEXT_PUBLIC_BLOG_DISPLAY_NAME="My Awesome Blog"
    NEXT_PUBLIC_BLOG_COPYRIGHT="© 2025 My Company"
    NEXT_DEFAULT_METADATA_DEFAULT_TITLE="My Blog Title"
    NEXT_PUBLIC_BLOG_DESCRIPTION="A description of my blog"
    NEXT_PUBLIC_BASE_URL="https://example.com"
    OG_IMAGE_SECRET="your_secure_secret"
  5. Initialize the Wisp CMS client

    master

    The project uses a pre-configured wisp client instance exported from @/lib/wisp. This client is built using buildWispClient from the @wisp-cms/client package and is initialized with a blogId sourced from the application configuration. You can import this wisp instance directly to perform CMS operations like fetching posts.

    import { wisp } from '@/lib/wisp';
    
    // Example usage: fetching posts
    const posts = await wisp.getPosts();
  6. Use the CommentForm component

    master

    The CommentForm component is a client-side React component used to submit comments on blog posts via the Wisp CMS. It handles form validation (using Zod), submission via the wisp.createComment method, and displays a pending verification message if the comment requires email verification.

    Props

    PropTypeDescription
    slugstringThe unique identifier (slug) of the blog post being commented on.
    configobjectConfiguration object controlling form behavior.
    parentIdstring (optional)The ID of a parent comment if this is a nested reply.
    onSuccess() => void (optional)Callback function executed after a successful submission.

    Configuration Object (config)

    KeyTypeDescription
    enabledbooleanWhether the comment form should be active.
    allowUrlsbooleanIf true, shows the 'Website' input field.
    allowNestedbooleanControls whether nested comments are supported (logic handled by parentId).
    signUpMessagestring | nullIf provided, displays a checkbox with this text for email usage consent.
    import { CommentForm } from "@/components/CommentForm";
    
    // Example usage within a page or component
    <CommentForm 
      slug="my-awesome-post" 
      config={{
        enabled: true,
        allowUrls: true,
        allowNested: true,
        signUpMessage: "Allow me to be notified of new comments via email?"
      }}
      onSuccess={() => console.log("Comment submitted!")}
    />
  7. Use the Header component

    master

    The Header component is a client-side component that renders the site title (linked to the home page) and the navigation menu. It automatically handles responsive design: displaying a horizontal list on medium screens (md:flex) and a mobile drawer/sheet menu on smaller screens (md:hidden).

    import { Header } from "@/components/Header";
    
    export default function Layout({ children }: { children: React.ReactNode }) {
      return (
        <>
          <Header />
          <main>{children}</main>
        </>
      );
    }
  8. Sign and verify Open Graph image parameters

    master

    To prevent unauthorized use of your Open Graph image generation service, you can sign the parameters using a secret key. This utility provides functions to generate a signature based on the title, label, and brand parameters, and to verify a provided signature against those parameters.

    Note: This requires config.ogImageSecret to be set in your configuration.

    import { signOgImageParams, verifyOgImageSignature } from "@/lib/og-image";
    
    const params = {
      title: "My Blog Post",
      label: "Tutorial",
      brand: "Wisp"
    };
    
    // 1. Generate signature
    const { valueString, signature } = signOgImageParams(params);
    
    // 2. Verify signature later
    const isValid = verifyOgImageSignature(params, signature);
  9. Create a comment via wisp.createComment

    master

    To programmatically create a comment (for example, when building a custom form), use the wisp.createComment method. This method expects a CreateCommentRequest object.

    CreateCommentRequest Shape

    FieldTypeDescription
    slugstringThe post slug.
    authorstringThe name of the commenter.
    emailstringThe commenter's email address.
    urlstring (optional)The commenter's website URL.
    contentstringThe actual comment text.
    allowEmailUsagebooleanWhether the user consents to email usage.
    parentIdstring (optional)The ID of the parent comment for replies.
    import { wisp } from "@/lib/wisp";
    
    await wisp.createComment({
      slug: "my-post-slug",
      author: "Jane Doe",
      email: "jane@example.com",
      content: "This is a great post!",
      allowEmailUsage: true,
      // url: "https://janedoe.com", // optional
      // parentId: "comment-id-123" // optional for replies
    });
  10. Generate a signed Open Graph image URL

    master

    Use signOgImageUrl to construct a full URL for the Open Graph image API endpoint. The function automatically appends the title, optional label and brand parameters, and a signature (s) to the query string. The base URL is derived from config.baseUrl and the path is hardcoded to /api/og-image/.

    import { signOgImageUrl } from "@/lib/og-image";
    
    const imageUrl = signOgImageUrl({
      title: "Hello World",
      label: "News",
      brand: "Wisp"
    });
    
    // Resulting URL format: 
    // https://your-domain.com/api/og-image/?title=Hello+World&label=News&brand=Wisp&s=...signature...
  11. Reference: Blog configuration environment variables

    master

    The following environment variables control the application's configuration object:

    NEXT_PUBLIC_BLOG_ID          - The unique identifier for your Wisp blog (REQUIRED).
    NEXT_PUBLIC_BLOG_DISPLAY_NAME - The display name of the blog (Default: "Travel.").
    NEXT_PUBLIC_BLOG_COPYRIGHT    - The copyright text (Default: "Samantha").
    NEXT_DEFAULT_METADATA_DEFAULT_TITLE - The default SEO title (Default: "Travel with Samantha").
    NEXT_PUBLIC_BLOG_DESCRIPTION - The default SEO description (Default: "Blog about travel and lifestyle.").
    NEXT_PUBLIC_BASE_URL          - The base URL of the site (Default: "http://localhost:3000").
    OG_IMAGE_SECRET               - Secret used for signing and verifying the OG image URL (Default: "secret_used_for_signing_and_verifying_the_og_image_url").