Next.js 14 terus mendominasi ekosistem React dengan App Router, Server Actions, dan streaming SSR. Tutorial ini memberi langkah demi langkah instalasi, konfigurasi, contoh kode, serta best practice untuk membangun aplikasi web modern yang siap produksi.

1. Prasyarat

Pastikan Anda memiliki:

  • Node.js v20.10 atau lebih baru (node -v)
  • npm v10 atau Yarn v4
  • Git untuk version control
  • Akun GitHub (untuk deployment ke Vercel atau Railway)

2. Membuat Proyek Next.js 14 Baru

npx create-next-app@latest my-next14-app --tsx --eslint --app
cd my-next14-app

Perintah di atas membuat proyek dengan:

  • TypeScript (--tsx)
  • Linting ESLint standar
  • Struktur app/ (App Router) otomatis

3. Instalasi Dependensi Tambahan

# Styling (Tailwind CSS 3.4 terbaru)
npm i -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p

# Prisma ORM untuk database (PostgreSQL)
npm i prisma@5.12 @prisma/client@5.12
npx prisma init --datasource-provider postgresql

# UI component library (shadcn/ui) – kompatibel dengan Server Actions
npx shadcn-ui@latest init

Konfigurasikan tailwind.config.js agar mendukung app/:

module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./app/**/*.{js,ts,jsx,tsx}",
    "./pages/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

4. Konfigurasi Database dengan Prisma

Ubah prisma/schema.prisma:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  createdAt DateTime @default(now())
}

Set environment variable di .env:

DATABASE_URL="postgresql://user:password@localhost:5432/next14_demo"

Run migration:

npx prisma migrate dev --name init

5. Membuat Server Action untuk Menyimpan Post

Server Actions memungkinkan fungsi async dijalankan di server langsung dari komponen React tanpa API route terpisah.

// app/actions.ts
import { prisma } from "@/lib/prisma";

export async function createPost(title: string, content?: string) {
  "use server"; // tells Next.js this runs on server
  return await prisma.post.create({
    data: { title, content },
  });
}

Import di komponen:

// app/create-post/page.tsx
"use client";
import { useState, FormEvent } from "react";
import { createPost } from "../actions";

export default function CreatePost() {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e: FormEvent) {
    e.preventDefault();
    setLoading(true);
    try {
      await createPost(title, content);
      alert("Post berhasil disimpan!");
    } catch (err) {
      console.error(err);
      alert("Gagal menyimpan post.");
    } finally {
      setLoading(false);
    }
  }

  return (
    
setTitle(e.target.value)} required className="w-full p-2 border rounded" />