Next.js
dari Nol sampai Vercel
Bangun aplikasi web modern dengan React, lalu online-kan dalam hitungan menit.
Tekan → / spasi untuk lanjut · M untuk daftar slide · F untuk fullscreen
Agenda Workshop
Pengenalan
Apa itu Next.js, kenapa dipakai, cara kerja rendering.Setup Project
create-next-app, struktur folder, menjalankan dev server.Routing
App Router, layout, dynamic route, navigasi.Server & Client
Server Components, Client Components, data fetching.Full-stack
Route Handlers, Server Actions, env, proxy.Deploy
GitHub → Vercel, domain, preview deployment.Yang Perlu Disiapkan
- Node.js 20.9+ (disarankan LTS terbaru)
- Git terpasang & sudah dikonfigurasi
- Akun GitHub
- Akun Vercel (login pakai GitHub)
- Code editor — VS Code direkomendasikan
- Dasar HTML, CSS, JavaScript & React
node -v # v20.9.0 atau lebih baru
npm -v
git --version
git config --global user.name "Nama Kamu"
git config --global user.email "[email protected]"Pengenalan Next.js
Framework React untuk production.
Apa itu Next.js?
React adalah library untuk membangun UI. Next.js adalah framework di atas React yang menyediakan semua yang dibutuhkan untuk aplikasi siap produksi.
- Routing berbasis file/folder
- Rendering di server (SSR / SSG / streaming)
- Backend dalam satu project (API & Server Actions)
- Optimasi gambar, font, dan bundle otomatis
Vercel
Open source, dipakai oleh Netflix, TikTok, Notion, Nike, dan banyak lagi.Next.js 16
App Router, React 19, Turbopack sebagai bundler default.React Saja vs Next.js
| Kebutuhan | React (Vite) | Next.js |
|---|---|---|
| Routing | Pasang library (React Router) | Bawaan — folder = URL |
| Rendering | Di browser (CSR) | Server, statis, atau browser — pilih per halaman |
| SEO | HTML awal kosong | HTML sudah berisi konten |
| Backend / API | Project terpisah | Route Handlers & Server Actions |
| Optimasi | Manual | next/image, next/font, code splitting |
| Deploy | Static hosting | Vercel (zero-config), Node server, Docker, static export |
Cara Halaman Dirender
Dirender di browser
Server kirim HTML kosong + JS. Cocok untuk dashboard interaktif di balik login.Dirender tiap request
HTML dibuat di server saat diminta. Data selalu terbaru, bagus untuk SEO.Dirender saat build
HTML dibuat sekali saatnext build. Paling cepat, bisa di-cache CDN.Statis + diperbarui berkala
Halaman statis yang dibangun ulang otomatis setiap N detik atau saat dipicu.Setup Project
Dari terminal kosong ke halaman pertama.
Membuat Project Baru
npx create-next-app@latest mini-blog
# Pilih "Yes, use recommended defaults"
# ✔ TypeScript
# ✔ ESLint
# ✔ Tailwind CSS
# ✔ App Router
# ✔ Turbopack
cd mini-blog
npm run devBuka http://localhost:3000 🎉
npm run dev— dev server + hot reloadnpm run build— build produksinpm start— jalankan hasil build
app/page.tsx, simpan, dan lihat browser langsung berubah.Struktur Folder
mini-blog/ ├── app/ # routing ada di sini │ ├── layout.tsx # layout root │ ├── page.tsx # halaman "/" │ ├── globals.css │ └── favicon.ico ├── public/ # file statis ├── next.config.ts ├── package.json ├── tsconfig.json └── .gitignore
app/— setiap folder adalah segmen URLpage.tsx— membuat route bisa diakseslayout.tsx— UI pembungkus (header, footer)public/— gambar & aset, diakses dari/nama-filenext.config.ts— konfigurasi Next.js
App Router
Folder = URL. File = UI.
Folder Menjadi URL
app/ ├── page.tsx → / ├── about/ │ └── page.tsx → /about └── blog/ ├── page.tsx → /blog └── [slug]/ └── page.tsx → /blog/apa-saja
export default function AboutPage() {
return (
<main>
<h1>Tentang Kami</h1>
<p>Belajar Next.js bareng!</p>
</main>
);
}page.tsx wajib di-export default.File Khusus di App Router
| File | Fungsi |
|---|---|
| page.tsx | UI unik untuk route; membuat route bisa diakses publik |
| layout.tsx | UI pembungkus yang dipakai bersama, tetap ada saat pindah halaman |
| loading.tsx | Tampilan loading otomatis (React Suspense) |
| error.tsx | Menangkap error di segmen tersebut |
| not-found.tsx | Halaman 404 |
| route.ts | Endpoint API (Route Handler) |
Plus konvensi folder: [slug] dinamis · (group) grup tanpa URL · _private tidak jadi route.
Layout
import Link from "next/link";
import "./globals.css";
export default function RootLayout({
children,
}: { children: React.ReactNode }) {
return (
<html lang="id">
<body>
<nav>
<Link href="/">Home</Link>
<Link href="/blog">Blog</Link>
<Link href="/about">About</Link>
</nav>
{children}
</body>
</html>
);
}- Root layout wajib dan harus berisi
<html>&<body> children= halaman yang sedang aktif- Layout bisa bersarang:
app/blog/layout.tsxhanya membungkus halaman blog - State layout tidak hilang saat navigasi
Navigasi Antar Halaman
import Link from "next/link";
<Link href="/blog">Lihat Blog</Link>
<Link href={"/blog/" + post.slug}>
{post.title}
</Link>"use client";
import { useRouter } from "next/navigation";
const router = useRouter();
router.push("/dashboard");- Pakai
<Link>, bukan<a>, untuk link internal - Navigasi tanpa reload halaman penuh
- Halaman tujuan di-prefetch otomatis saat link terlihat
useRouter,usePathname,useSearchParamsdarinext/navigation- Redirect di server:
redirect("/login")
Dynamic Route
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>Artikel: {slug}</h1>;
}- Folder
[slug]menangkap segmen URL apa pun /blog/halo-dunia→slug = "halo-dunia"[...slug]menangkap banyak segmen
params & searchParams adalah Promise — wajib di-await.Buat Kerangka Mini Blog
- Ganti isi
app/page.tsxdengan sambutan sederhana - Buat
app/about/page.tsx - Buat
app/blog/page.tsx(daftar artikel — sementara teks dulu) - Buat
app/blog/[slug]/page.tsxyang menampilkanslug - Tambahkan navbar dengan
<Link>diapp/layout.tsx
/, /about, /blog, dan /blog/coba-123.Server & Client Components
Konsep paling penting di Next.js modern.
Server vs Client Components
Server Component
- Dirender di server, JS-nya tidak dikirim ke browser
- Bisa
async& akses database / API rahasia langsung - Tidak bisa pakai
useState,onClick, dsb.
Client Component
- Interaktif: state, event, effect
- Bisa akses API browser (
window,localStorage) - Tetap di-pre-render di server, lalu "dihidupkan" di browser
"use client" hanya di komponen kecil yang butuh interaksi.Menggabungkan Keduanya
"use client";
import { useState } from "react";
export default function LikeButton() {
const [likes, setLikes] = useState(0);
return (
<button onClick={() => setLikes(likes + 1)}>
❤️ {likes}
</button>
);
}import LikeButton from "@/app/components/LikeButton";
export default async function Page({ params }) {
const { slug } = await params;
const post = await getPost(slug); // di server
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
<LikeButton /> {/* pulau interaktif */}
</article>
);
}Mengambil Data di Server
import Link from "next/link";
type Post = { id: number; title: string };
const API = "https://jsonplaceholder.typicode.com";
export default async function BlogPage() {
const res = await fetch(API + "/posts");
const posts: Post[] = await res.json();
return (
<ul>
{posts.slice(0, 10).map((p) => (
<li key={p.id}>
<Link href={"/blog/" + p.id}>{p.title}</Link>
</li>
))}
</ul>
);
}- Cukup jadikan komponen
asynclaluawait - Tidak perlu
useEffect+ loading state manual - Bisa langsung query database (Prisma, Drizzle, dll.)
- API key aman — kode tidak pernah sampai ke browser
Caching & Revalidasi
// cache hasil fetch, perbarui tiap 60 detik
const res = await fetch(url, {
next: { revalidate: 60 },
});import { revalidatePath } from "next/cache";
revalidatePath("/blog");- Secara default data diambil setiap request (dinamis)
- Gunakan
revalidateagar hasilnya di-cache & diperbarui berkala revalidatePath/revalidateTaguntuk memperbarui cache sesuai kebutuhan
"use cache" (aktifkan cacheComponents di config) untuk caching per komponen/fungsi.Loading, Error & Not Found
export default function Loading() {
return <p>Memuat artikel...</p>;
}import { notFound } from "next/navigation";
const res = await fetch(url);
if (!res.ok) notFound(); // → not-found.tsx"use client";
export default function Error({
error, reset,
}: { error: Error; reset: () => void }) {
return (
<div>
<p>Ups: {error.message}</p>
<button onClick={reset}>Coba lagi</button>
</div>
);
}error.tsx wajib Client Component. loading.tsx otomatis membungkus halaman dengan <Suspense>.Isi Blog dengan Data Asli
- Di
app/blog/page.tsx, ambil daftar post darijsonplaceholder.typicode.com/posts - Di
app/blog/[slug]/page.tsx, ambil/posts/{slug}dan tampilkan judul + isi - Panggil
notFound()jika post tidak ada (coba/blog/9999) - Tambahkan
loading.tsxdanerror.tsxdi folderblog - Buat
LikeButtonsebagai Client Component dan pasang di halaman detail
Fitur Full-stack
Backend tanpa project terpisah.
Route Handlers (API)
export async function GET() {
return Response.json({ message: "Halo dari API!" });
}
export async function POST(request: Request) {
const body = await request.json();
return Response.json(
{ diterima: body },
{ status: 201 }
);
}- File
route.ts→ endpoint di URL foldernya - Export fungsi sesuai HTTP method:
GET,POST,PUT,DELETE… - Pakai Web API standar:
Request&Response - Cocok untuk webhook, integrasi pihak ketiga, aplikasi mobile
http://localhost:3000/api/helloServer Actions
"use server";
export async function kirimPesan(formData: FormData) {
const nama = formData.get("nama");
const pesan = formData.get("pesan");
// simpan ke database, kirim email, dll.
console.log({ nama, pesan });
}import { kirimPesan } from "./actions";
export default function ContactPage() {
return (
<form action={kirimPesan}>
<input name="nama" placeholder="Nama" />
<textarea name="pesan" />
<button type="submit">Kirim</button>
</form>
);
}fetch manual. Tetap jalan meski JavaScript belum termuat.Styling
<h1 className="text-3xl font-bold text-blue-600">
Mini Blog
</h1>import styles from "./Card.module.css";
<div className={styles.card}>...</div>- Tailwind CSS — utility class, sudah terpasang jika memilih default
- CSS Modules — class otomatis unik per komponen
- Global CSS —
app/globals.css, di-import di root layout - Library UI populer: shadcn/ui, Radix, Mantine
next/image & next/font
import Image from "next/image";
<Image
src="/hero.png" // dari folder public/
alt="Hero"
width={800}
height={400}
priority
/>import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
<body className={inter.className}>
{children}
</body>Image: resize otomatis, format modern (WebP/AVIF), lazy load, mencegah layout shiftnext/font: font di-host sendiri saat build — tanpa request ke Google, tanpa kedip
Metadata & SEO
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Mini Blog",
description: "Belajar Next.js",
};export async function generateMetadata({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return { title: post.title };
}sitemap.ts, robots.ts, opengraph-image.png, icon.png.Environment Variables
# hanya bisa dibaca di server
DATABASE_URL="postgres://..."
API_SECRET="rahasia"
# bisa dibaca di browser juga
NEXT_PUBLIC_SITE_NAME="Mini Blog"- Akses dengan
process.env.NAMA_VARIABEL - Tanpa prefix → hanya server (aman untuk rahasia)
- Prefix
NEXT_PUBLIC_→ ikut ke bundle browser .env.localsudah ada di.gitignore
proxy.ts (dulu middleware.ts)
import { NextResponse, type NextRequest } from "next/server";
export function proxy(request: NextRequest) {
const token = request.cookies.get("token");
if (!token) {
return NextResponse.redirect(
new URL("/login", request.url)
);
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};- Berjalan sebelum request sampai ke halaman
- Untuk redirect, rewrite, cek login sederhana, header
- Next.js 16 mengganti nama
middleware.ts→proxy.ts - Jaga tetap ringan — logika berat taruh di halaman/API
Tambahkan Fitur Full-stack
- Buat
app/api/hello/route.tsyang mengembalikan JSON - Buat halaman
/contactdengan form + Server Action - Tambahkan
metadatadi layout &generateMetadatadi detail post - Simpan nama situs di
.env.localsebagaiNEXT_PUBLIC_SITE_NAMEdan tampilkan di navbar - Rapikan tampilan dengan Tailwind
npm run build berhasil tanpa error.Deploy ke Vercel
Dari localhost ke internet.
Cek Build Lokal Dulu
npm run build
npm startOutput build menampilkan tiap route dan jenisnya:
○Static — di-render saat buildƒDynamic — di-render saat request
Push ke GitHub
# create-next-app sudah menjalankan git init
git add .
git commit -m "feat: mini blog"
# buat repo kosong di github.com/new,
# salin URL-nya, lalu:
git branch -M main
git remote add origin <URL-REPO-KAMU>
git push -u origin main- Buat repo di github.com/new — jangan centang "Add README"
- Repo boleh public atau private
- Pastikan
.env.local&node_modulestidak ikut ter-push
Import Project di Vercel
- Buka vercel.com → Sign Up / Log in with GitHub
- Klik Add New… → Project
- Pilih repo
mini-blog→ Import (beri izin akses repo jika diminta) - Framework Preset terdeteksi otomatis: Next.js — build command tidak perlu diubah
- Buka Environment Variables, isi variabel dari
.env.local - Klik Deploy dan tunggu ± 1 menit 🚀
mini-blog-xxxx.vercel.app dengan HTTPS otomatis.Git Push = Deploy
Production
Setiap push kemain memperbarui situs utama.Preview Deployment
Tiap branch/PR mendapat URL sendiri untuk dicek sebelum di-merge.Deploy lewat Vercel CLI
npm i -g vercel
vercel login
vercel # deploy preview
vercel --prod # deploy production
# ambil env dari Vercel ke lokal
vercel env pull .env.local- Cocok jika tidak ingin lewat GitHub
- Pertama kali jalan, CLI akan bertanya & menautkan project
vercel env pullmenyamakan env lokal dengan Vercelvercel logsuntuk melihat log
Pasang Domain Sendiri
- Project → Settings → Domains
- Masukkan domain, mis.
blog.domainku.com - Tambahkan record DNS yang ditampilkan Vercel di penyedia domain
- Tunggu verifikasi — SSL dibuat otomatis
| Jenis | Contoh record |
|---|---|
| Subdomain | CNAME → nilai dari Vercel(mis. cname.vercel-dns.com) |
| Apex / root | A → IP dari Vercel |
Masalah yang Sering Muncul
| Gejala | Solusi |
|---|---|
| Build gagal di Vercel | Jalankan npm run build lokal, baca log di tab Deployments |
| Module not found | Huruf besar/kecil nama file harus sama persis (Linux case-sensitive) |
| Env bernilai undefined | Tambahkan di Vercel lalu redeploy; cek prefix NEXT_PUBLIC_ |
| Data tidak berubah | Halaman di-render statis — pakai revalidate atau revalidatePath |
| Error hydration | Output server & client beda (mis. Date.now(), window saat render) |
Yang Sudah Kita Pelajari
📁 Routing
Folder = URL,page, layout, [slug]🖥 Server-first
Server Components,async fetch, "use client" seperlunya⚡ UX
loading, error, not-found, Image, Font🔌 Full-stack
Route Handlers & Server Actions🔐 Konfigurasi
Env variables, metadata,proxy.ts🚀 Deploy
GitHub → Vercel, preview, domainBelajar Lebih Lanjut
- Database: Postgres (Neon / Supabase) + Prisma atau Drizzle
- Autentikasi: Auth.js, Clerk, Better Auth
- UI: shadcn/ui + Tailwind
- Validasi: Zod untuk form & Server Actions
- Caching lanjutan:
"use cache"& Cache Components
nextjs.org/docs
Referensi resmi & tutorial lengkap.nextjs.org/learn
Membangun dashboard full-stack langkah demi langkah.Terima kasih!
Sekarang aplikasimu sudah online. Bagikan URL Vercel-mu di grup!
Slide: nextjs.dihapra.dev · Sesi tanya jawab