How to Build a Simple Blog Using ElmapiCMS and Next.js

Learn how to build a simple blog from scratch using ElmapiCMS as your headless CMS and Next.js as your frontend framework. This step-by-step tutorial covers setup, configuration, and implementation.

R
Raşit Apalak
·
·
9 min read

A Step-by-Step Guide to Creating Your First Blog with ElmapiCMS and Next.js

In this tutorial, you'll learn how to build a simple blog using ElmapiCMS and Next.js. We'll walk through setting up your ElmapiCMS instance, creating a blog collection, connecting it to a Next.js application, and displaying your content. By the end, you'll have a fully functional blog that you can customize and extend.


Table of Contents


What You'll Build

By the end of this tutorial, you'll have:

  • A Next.js blog application with a list page showing all posts
  • Individual blog post pages with full content
  • Integration with ElmapiCMS for content management
  • A clean, responsive design
  • A foundation you can extend with features like pagination, categories, or search

Prerequisites

Before you begin, make sure you have:

  • Node.js 18 or higher installed
  • An ElmapiCMS instance running (local or remote)
  • Basic knowledge of React and Next.js
  • A code editor like VS Code or Cursor

If you don't have ElmapiCMS installed yet, check out our installation guide to get started.


Step 1: Set Up ElmapiCMS

First, you need to have ElmapiCMS running. If you haven't installed it yet, follow the installation docs for your environment (CodeCanyon or GitHub, then your OS).

Once ElmapiCMS is running:

  1. Login to your ElmapiCMS instance.
  2. Create a new project by clicking "Create Project"
  3. Give it a name like "My Blog" and click "Create Project"

You'll be taken to your project dashboard where you can start creating collections.


Step 2: Create a Blog Collection

Now let's create a collection to store your blog posts. ElmapiCMS has predefined collection templates. We will use the "Blog Post" template.

  1. In your ElmapiCMS project, click "+ Add New" button in the "Collections" sidebar.
  2. Name it "Posts" (this will be the collection identifier)
  3. Chose "Blog Post" as the template
  4. Click "Create Collection"

This will create a new collection with 3 fields: title, slug and content. You can see the fields list in the collection settings by clicking the setting icon next to the collection name. You can add, edit or delete fields here.


Step 3: Add Sample Content

Let's add a few sample blog posts to test with:

  1. Click colleciton name in the collection list.
  2. Click "+ Create New"
  3. Fill in the fields:
    • Title: "Welcome to My Blog"
    • Slug: "welcome-to-my-blog" (auto-generated from title)
    • Content: "This is my first blog post using ElmapiCMS and Next.js!"
  4. Click "Save & Publish"

Repeat this process to add 2-3 more sample posts. This will give us content to display in our Next.js application.

Tip: You can use AI to generate content for your blog posts. See our tutorial on how to generate content with AI using ElmapiCMS Import/Export feature for more details.


Step 4: Create a Next.js Project

Now let's create a new Next.js project. Open your terminal and run:

npx create-next-app@latest my-blog

When prompted, "Would you like to use the recommended Next.js defaults?" "Yes, use recommended defaults." will be selected by default. Press Enter to continue.

This will create a new Next.js project with the latest features.

Go to the project directory:

cd my-blog

Step 5: Install the ElmapiCMS SDK

Next, we need to install the ElmapiCMS JavaScript SDK to interact with our CMS:

npm install @elmapicms/js-sdk

The SDK provides a simple interface to fetch content from your ElmapiCMS instance.


Step 6: Configure Environment Variables

Create a .env.local file in your Next.js project root:

ELMAPI_PROJECT_ID=your-project-uuid-here
ELMAPI_API_KEY=your-api-key-here
ELMAPI_API_URL=http://localhost:8000/api
ELMAPI_IMAGE_HOST=localhost:8000

To find your Project ID:

  1. Go to your ElmapiCMS project
  2. Navigate to API Access
  3. Copy the Project ID
  4. Paste it into your .env.local file

To create your API key:

  1. Go to your ElmapiCMS project
  2. Navigate to API Access
  3. Click "Create Token"
  4. Copy the API key
  5. Paste it into your .env.local file

Tip: You can also enable "Public GET Access" in the same page. You don't need to create a token if you enable this.

If your ElmapiCMS instance is hosted remotely, update the ELMAPI_API_URL and ELMAPI_IMAGE_HOST accordingly.


Step 7: Create the API Client

Create a new file lib/elmapicms.ts to set up the ElmapiCMS client:

import { createClient } from '@elmapicms/js-sdk';

const apiUrl = process.env.ELMAPI_API_URL || 'http://localhost:8000/api';
const projectId = process.env.ELMAPI_PROJECT_ID || '';
const apiKey = process.env.ELMAPI_API_KEY || '';
if (!projectId) {
  throw new Error('ELMAPI_PROJECT_ID environment variable is required');
}

export const client = createClient(apiUrl, apiKey, projectId);

export interface BlogPost {
  uuid: string;
  locale: string;
  published_at: string | null;
  fields: {
    title: string;
    slug: string;
    content: string;
    excerpt?: string;
    published_date?: string;
  };
}

export async function getAllPosts(): Promise<BlogPost[]> {
  try {
    const entries = await client.getEntries('posts');
    return Array.isArray(entries) ? entries : [];
  } catch (error) {
    console.error('Error fetching posts:', error);
    return [];
  }
}

export async function getPostBySlug(slug: string): Promise<BlogPost | null> {
  try {
    const posts = await getAllPosts();
    return posts.find((post) => post.fields.slug === slug) || null;
  } catch (error) {
    console.error('Error fetching post:', error);
    return null;
  }
}

This file exports:

  • A configured ElmapiCMS client
  • TypeScript interfaces for type safety
  • Helper functions to fetch all posts and individual posts by slug

Step 8: Build the Blog List Page

Now let's create the main blog page that lists all posts. Update app/page.tsx:

import Link from 'next/link';
import { getAllPosts } from '@/lib/elmapicms';

export default async function Home() {
  const posts = await getAllPosts();

  return (
    <div className="container mx-auto px-4 py-8">
      <header className="mb-12">
        <h1 className="text-4xl font-bold mb-4">My Blog</h1>
        <p className="text-gray-600">Welcome to my blog powered by ElmapiCMS and Next.js</p>
      </header>

      <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
        {posts.length === 0 ? (
          <p className="text-gray-500">No posts found. Add some posts in your ElmapiCMS admin panel.</p>
        ) : (
          posts.map((post) => (
            <article key={post.uuid} className="border rounded-lg p-6 hover:shadow-lg transition-shadow">
              <h2 className="text-2xl font-semibold mb-2">
                <Link 
                  href={`/blog/${post.fields.slug}`}
                  className="hover:text-blue-600"
                >
                  {post.fields.title}
                </Link>
              </h2>
              
              {post.fields.published_date && (
                <time className="text-sm text-gray-500 block mb-3">
                  {new Date(post.fields.published_date).toLocaleDateString('en-US', {
                    year: 'numeric',
                    month: 'long',
                    day: 'numeric',
                  })}
                </time>
              )}

              {post.fields.excerpt && (
                <p className="text-gray-700 mb-4">{post.fields.excerpt}</p>
              )}

              <Link 
                href={`/blog/${post.fields.slug}`}
                className="text-blue-600 hover:underline"
              >
                Read more →
              </Link>
            </article>
          ))
        )}
      </div>
    </div>
  );
}

This page:

  • Fetches all posts from ElmapiCMS
  • Displays them in a responsive grid
  • Shows the title, date, and excerpt for each post
  • Links to individual post pages

Step 9: Create the Blog Post Page

Create a new directory and file app/blog/[slug]/page.tsx for individual blog posts:

import { notFound } from 'next/navigation';
import Link from 'next/link';
import { getPostBySlug, getAllPosts } from '@/lib/elmapicms';

export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({
    slug: post.fields.slug,
  }));
}

export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await getPostBySlug(slug);

  if (!post) {
    notFound();
  }

  return (
    <div className="container mx-auto px-4 py-8 max-w-4xl">
      <Link 
        href="/" 
        className="text-blue-600 hover:underline mb-8 inline-block"
      >
        ← Back to Blog
      </Link>

      <article className="prose prose-lg max-w-none">
        <h1 className="text-4xl font-bold mb-4">{post.fields.title}</h1>
        
        {post.fields.published_date && (
          <time className="text-gray-500 block mb-8">
            {new Date(post.fields.published_date).toLocaleDateString('en-US', {
              year: 'numeric',
              month: 'long',
              day: 'numeric',
            })}
          </time>
        )}

        <div 
          className="blog-content"
          dangerouslySetInnerHTML={{ __html: post.fields.content }}
        />
      </article>
    </div>
  );
}

This page:

  • Fetches a single post by slug
  • Uses generateStaticParams for static generation
  • Displays the full post content
  • Includes a back link to the blog list

Step 10: Add Styling

If you're using Tailwind CSS (which we set up earlier), the styling is already included in the code above. If you want to add custom styles for the blog content, you can add this to your globals.css:

.blog-content {
  line-height: 1.8;
}

.blog-content h2 {
  font-size: 2rem;
  font-weight: bold;
  margin-top: 2rem;
  margin-bottom: 1rem;
}

.blog-content p {
  margin-bottom: 1.5rem;
}

.blog-content ul,
.blog-content ol {
  margin-left: 2rem;
  margin-bottom: 1.5rem;
}

.blog-content code {
  background-color: #f4f4f4;
  padding: 0.2rem 0.4rem;
  border-radius: 0.25rem;
  font-size: 0.9em;
}

Testing Your Blog

Now let's test your blog:

  1. Make sure your ElmapiCMS instance is running
  2. Start your Next.js development server:
    npm run dev
    
  3. Open http://localhost:3000 in your browser
  4. You should see your blog posts listed
  5. Click on a post to view the full content

If your ElmapiCMS instance is hosted locally you may encounter this error:

Error [NetworkError]: unable to verify the first certificate

To fix this you can run the following command:

NODE_TLS_REJECT_UNAUTHORIZED=0 npm run dev

This will allow you to access your blog locally without encountering the certificate error. This is a temporary solution and you should not use it in production.


Troubleshooting

If you encounter issues:

  • Posts not showing: Check that your ELMAPI_PROJECT_ID is correct and your ElmapiCMS instance is running
  • API errors: Verify your ELMAPI_API_URL matches your ElmapiCMS instance URL
  • Build errors: Make sure all environment variables are set in .env.local
  • Type errors: Ensure you've installed the ElmapiCMS SDK and TypeScript is properly configured

For more help, visit the ElmapiCMS documentation or check out our getting started guide.


Conclusion

Congratulations! You've successfully built a simple blog using ElmapiCMS and Next.js. You now have:

  • A headless CMS setup with ElmapiCMS
  • A Next.js frontend that fetches and displays content
  • A foundation you can extend with additional features

ElmapiCMS makes it easy to manage your content while Next.js provides a fast, modern frontend. This combination gives you the flexibility to scale your blog as it grows, add new features, and maintain full control over your content and code.

Share this post:

Ready to build your content API?

Try ElmapiCMS free with our live demo, or get the full source code for $149.

Comparing options first? Read our best headless CMS comparison.

Related Posts