Next.js 14 menjadi standar de‑facto untuk aplikasi React modern. Tutorial ini membimbing Anda langkah demi langkah menginstal, mengkonfigurasi, dan mengoptimalkan proyek Next.js 14 dengan TypeScript, Turbopack, dan best practice terbaru untuk performance dan security.

1. Persiapan Lingkungan

Pastikan Anda menggunakan Node.js LTS 20.12 atau lebih baru serta npm atau pnpm versi 9+. Verifikasi versi dengan:

node -v
npm -v   # atau pnpm -v

Jika belum terpasang, download Node.js dari nodejs.org dan instal pnpm secara global:

npm install -g pnpm

2. Membuat Projek Next.js 14

Gunakan starter resmi yang sudah mengaktifkan App Router dan Turbopack secara default.

pnpm create next-app@latest my-next14-app \
  --ts                # TypeScript
  --eslint            # Linter
  --app               # Aktifkan App Router
  --turbo

Setelah proses selesai, masuk ke folder proyek:

cd my-next14-app

3. Struktur Direktori Baru (App Router)

Next.js 14 memindahkan logika ke folder app. Contoh struktur yang diharapkan:

app/
  layout.tsx          # Root layout
  page.tsx            # Halaman utama
  globals.css
  (dashboard)/
    layout.tsx
    page.tsx
    loading.tsx
  api/
    route.ts          # API route modern
public/
  favicon.ico
  ...

Perhatikan bahwa pages sudah tidak dipakai kecuali untuk migrasi legacy.

4. Instalasi Dependensi Tambahan

Tambahkan library yang paling umum dipakai di 2026:

  • react-hook-form – form handling
  • zod – schema validation
  • next-auth – otentikasi
  • next-seo – SEO meta tags
  • @tanstack/react-query – data fetching
pnpm add react-hook-form zod next-auth next-seo @tanstack/react-query

Untuk dev dependencies, install ESLint konfigurasi Next.js dan stylelint:

pnpm add -D eslint-config-next stylelint stylelint-config-standard

5. Konfigurasi Turbopack (Opsional)

Next.js 14 menggantikan Webpack dengan Turbopack secara default, tetapi Anda dapat menyesuaikannya lewat next.config.mjs:

import { defineConfig } from 'next/config';

export default defineConfig({
  compiler: {
    // Enable React Server Components secara eksplisit
    reactServerComponents: true,
  },
  experimental: {
    // Aktifkan caching edge per‑route
    serverActions: true,
  },
  // Optimasi bundling Turbopack
  turbo: {
    rules: {
      // Contoh mengexclude large assets dari cache
      '*.{png,jpg,svg}': { type: 'asset', parser: { dataUrlCondition: { maxSize: 8 * 1024 } } },
    },
  },
});

6. Menambahkan Layout Global dengan Tailwind CSS

Tailwind tetap populer pada 2026. Install dan inisialisasi:

pnpm add -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p

Update tailwind.config.cjs:

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

Tambahkan Tailwind directives ke app/globals.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

Modifikasi app/layout.tsx untuk menyertakan CSS:

import './globals.css';

export const metadata = {
  title: 'Next.js 14 Starter',
  description: 'Demo aplikasi modern dengan App Router',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    
      
        {children}
      
    
  );
}

7. Membuat API Route dengan Edge Runtime

Next.js 14 mendukung Edge Runtime secara native. Buat file app/api/hello/route.ts:

import { NextResponse } from 'next/server';

export const runtime = 'edge';

export async function GET(request: Request) {
  const hello = { message: 'Hello from Edge Runtime! 🚀' };
  return NextResponse.json(hello);
}

Uji dengan pnpm dev dan buka http://localhost:3000/api/hello.

8. Implementasi Form dengan react-hook-form + Zod

Buat komponen sederhana di components/ContactForm.tsx:

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(2, 'Nama terlalu pendek'),
  email: z.string().email('Email tidak valid'),
  message: z.string().min(10, 'Pesan minimal 10 karakter'),
});

type FormData = z.infer;

export default function ContactForm() {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data: FormData) => {
    const res = await fetch('/api/contact', { method: 'POST', body: JSON.stringify(data) });
    if (!res.ok) {
      // handle error
    }
    // success handling
  };

  return (
    
{errors.name &&

{errors.name.message}

}
{errors.email &&

{errors.email.message}

}