Tutorial Membuat Blog dengan Next.js, TailwindCSS dan Sanity CMS - Nayaka Yoga Pradipta

Tutorial Membuat Blog dengan Next.js, TailwindCSS dan Sanity CMS

Minggu, 22 Des 2024

Membangun blog modern tidak lagi serumit dulu. Dengan kombinasi Next.js, TailwindCSS, dan Sanity CMS, kamu bisa membuat blog yang cepat, SEO-friendly, dan mudah di-manage. Dalam tutorial ini, saya akan memandu kamu step-by-step dari nol hingga deploy.

Apa yang Akan Kita Bangun?

Kita akan membuat blog dengan fitur:

  • Homepage dengan daftar artikel
  • Halaman detail artikel dengan konten dari Sanity
  • Styling modern dengan TailwindCSS
  • CMS dashboard untuk mengelola konten

Prasyarat

  • Node.js versi 18 atau lebih baru
  • Text editor (VS Code recommended)
  • Akun Sanity (gratis)
  • Pengetahuan dasar React dan JavaScript

Step 1: Setup Project Next.js

Buka terminal dan jalankan command berikut untuk membuat project Next.js baru:

npx create-next-app@latest my-blog
cd my-blog

Next.js Installation

Saat ditanya, pilih opsi berikut:

  • TypeScript: Yes
  • ESLint: Yes
  • Tailwind CSS: Yes
  • src/ directory: Yes
  • App Router: Yes

Step 2: Setup Sanity CMS

Install Sanity CLI dan buat project Sanity di dalam folder Next.js:

npm create sanity@latest -- --template clean --create-project "My Blog" --dataset production --output-path sanity

Sanity Homepage

Ini akan membuat folder sanity di dalam project kamu dengan Sanity Studio.

Step 3: Buat Schema untuk Blog Post

Buat file schema untuk blog post di sanity/schemaTypes/post.ts:

import { defineField, defineType } from 'sanity'

export default defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      title: 'Title',
      type: 'string',
      validation: (Rule) => Rule.required(),
    }),
    defineField({
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
      validation: (Rule) => Rule.required(),
    }),
    defineField({
      name: 'excerpt',
      title: 'Excerpt',
      type: 'text',
      rows: 3,
    }),
    defineField({
      name: 'mainImage',
      title: 'Main image',
      type: 'image',
      options: {
        hotspot: true,
      },
    }),
    defineField({
      name: 'publishedAt',
      title: 'Published at',
      type: 'datetime',
    }),
    defineField({
      name: 'body',
      title: 'Body',
      type: 'array',
      of: [
        { type: 'block' },
        { type: 'image' },
      ],
    }),
  ],
})

Sanity Schema

Lalu register schema di sanity/schemaTypes/index.ts:

import post from './post'

export const schemaTypes = [post]

Step 4: Jalankan Sanity Studio

Masuk ke folder sanity dan jalankan studio:

cd sanity
npm run dev

Sanity Studio

Buka http://localhost:3333 dan buat beberapa post untuk testing.

Step 5: Install Sanity Client di Next.js

Kembali ke root project dan install dependencies:

cd ..
npm install @sanity/client @sanity/image-url @portabletext/react

Buat file src/lib/sanity.ts untuk konfigurasi client:

import { createClient } from '@sanity/client'
import imageUrlBuilder from '@sanity/image-url'

export const client = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
  dataset: 'production',
  useCdn: true,
  apiVersion: '2024-01-01',
})

const builder = imageUrlBuilder(client)

export function urlFor(source: any) {
  return builder.image(source)
}

Tambahkan environment variable di .env.local:

NEXT_PUBLIC_SANITY_PROJECT_ID=your_project_id

Step 6: Buat Halaman Blog List

Edit src/app/page.tsx untuk menampilkan daftar blog:

import { client, urlFor } from '@/lib/sanity'
import Link from 'next/link'
import Image from 'next/image'

async function getPosts() {
  const query = `*[_type == "post"] | order(publishedAt desc) {
    _id,
    title,
    slug,
    excerpt,
    mainImage,
    publishedAt
  }`
  return client.fetch(query)
}

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

  return (
    <main className="max-w-4xl mx-auto px-4 py-12">
      <h1 className="text-4xl font-bold mb-8">My Blog</h1>
      <div className="grid gap-8">
        {posts.map((post: any) => (
          <Link 
            key={post._id} 
            href={`/blog/${post.slug.current}`}
            className="group"
          >
            <article className="border rounded-lg overflow-hidden hover:shadow-lg transition">
              {post.mainImage && (
                <Image
                  src={urlFor(post.mainImage).width(800).height(400).url()}
                  alt={post.title}
                  width={800}
                  height={400}
                  className="w-full h-48 object-cover"
                />
              )}
              <div className="p-6">
                <h2 className="text-2xl font-semibold group-hover:text-blue-600">
                  {post.title}
                </h2>
                <p className="text-gray-600 mt-2">{post.excerpt}</p>
              </div>
            </article>
          </Link>
        ))}
      </div>
    </main>
  )
}

Blog Demo Homepage

Step 7: Buat Halaman Detail Post

Buat file src/app/blog/[slug]/page.tsx:

import { client, urlFor } from '@/lib/sanity'
import { PortableText } from '@portabletext/react'
import Image from 'next/image'
import { notFound } from 'next/navigation'

async function getPost(slug: string) {
  const query = `*[_type == "post" && slug.current == $slug][0] {
    _id,
    title,
    slug,
    mainImage,
    publishedAt,
    body
  }`
  return client.fetch(query, { slug })
}

export default async function BlogPost({ 
  params 
}: { 
  params: { slug: string } 
}) {
  const post = await getPost(params.slug)

  if (!post) {
    notFound()
  }

  return (
    <article className="max-w-3xl mx-auto px-4 py-12">
      {post.mainImage && (
        <Image
          src={urlFor(post.mainImage).width(1200).height(600).url()}
          alt={post.title}
          width={1200}
          height={600}
          className="w-full rounded-lg mb-8"
        />
      )}
      <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
      <time className="text-gray-500 mb-8 block">
        {new Date(post.publishedAt).toLocaleDateString('id-ID', {
          year: 'numeric',
          month: 'long',
          day: 'numeric'
        })}
      </time>
      <div className="prose prose-lg max-w-none">
        <PortableText value={post.body} />
      </div>
    </article>
  )
}

Step 8: Styling dengan TailwindCSS

Install plugin typography untuk styling konten:

npm install @tailwindcss/typography

Tailwind Typography

Tambahkan ke tailwind.config.ts:

import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './src/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [
    require('@tailwindcss/typography'),
  ],
}

export default config

Step 9: Konfigurasi Next.js untuk Gambar

Tambahkan domain Sanity di next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.sanity.io',
      },
    ],
  },
}

module.exports = nextConfig

Step 10: Jalankan Project

Sekarang jalankan project Next.js:

npm run dev

Buka http://localhost:3000 dan lihat blog kamu!

Kesimpulan

Selamat! Kamu sudah berhasil membuat blog dengan Next.js, TailwindCSS, dan Sanity CMS. Kombinasi ini sangat powerful karena:

  1. Next.js memberikan performa dan SEO yang excellent
  2. TailwindCSS membuat styling cepat dan konsisten
  3. Sanity CMS memberikan dashboard yang user-friendly untuk mengelola konten

Ada pertanyaan? Reach out ke saya di Twitter @nayakayp!