Web Development

Clerk: Authentication Platform

Tutorial lengkap Clerk Authentication dari nol β€” setup, komponen UI, user management, webhooks, organizations, dan integrasi dengan Next.js

1. Pengenalan Clerk

Clerk adalah platform autentikasi dan user management yang menyediakan solusi lengkap untuk login, registrasi, manajemen profil, dan kontrol akses β€” tanpa perlu membangun semuanya dari nol. Clerk memberikan komponen UI siap pakai, SDK yang kaya, dan dashboard admin yang powerful.

Clerk mendukung berbagai metode autentikasi: email/password, social login (Google, GitHub, Apple, dll.), passwordless (magic link, OTP), multi-factor authentication (MFA), dan SSO (SAML).

Mengapa Menggunakan Clerk?

KeunggulanPenjelasan
Drop-in UI ComponentsKomponen login, signup, profil siap pakai β€” tinggal render
Multi-FrameworkNext.js, React, Remix, Astro, Express, dan banyak lagi
Social LoginGoogle, GitHub, Apple, Facebook, Microsoft, dan 20+ lainnya
OrganizationsMulti-tenancy, team management, role-based access control
WebhooksReal-time events untuk sinkronisasi dengan database Anda
User ManagementDashboard admin untuk mengelola semua user

Clerk vs Alternatif Lain

AspekClerkNextAuth.jsFirebase AuthAuth0
UI Componentsβœ… Built-in❌ Manual🟑 Widget🟑 Universal Login
Setup Time⚑ 5 menit🟑 30 menit🟑 15 menitπŸ”΄ 1 jam
Organizationsβœ… Built-in❌ Manual❌ Tidak adaβœ… Built-in
Free Tier10.000 MAUGratis (self-host)50.000 MAU7.500 MAU
Self-hosted❌ Tidakβœ… Ya❌ Tidak❌ Tidak
Learning Curve🟒 Mudah🟑 Sedang🟒 MudahπŸ”΄ Curam
Diagram: Arsitektur Clerk Authentication
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                CLERK ARCHITECTURE                     β”‚
β”‚                                                      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Your Application   β”‚  β”‚    Clerk Dashboard    β”‚  β”‚
β”‚  β”‚                      β”‚  β”‚    (clerk.com)        β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚                      β”‚  β”‚
β”‚  β”‚  β”‚ Clerk Provider β”‚  β”‚  β”‚  β€’ App Settings      β”‚  β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚  β€’ User Management   β”‚  β”‚
β”‚  β”‚          β”‚           β”‚  β”‚  β€’ Appearance Config  β”‚  β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚  β€’ Social Connectionsβ”‚  β”‚
β”‚  β”‚  β”‚ UI Components  β”‚  β”‚  β”‚  β€’ Webhooks Config   β”‚  β”‚
β”‚  β”‚  β”‚ <SignIn />     β”‚  β”‚  β”‚  β€’ Organizations     β”‚  β”‚
β”‚  β”‚  β”‚ <SignUp />     β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”‚  β”‚ <UserButton /> β”‚  β”‚                           β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚                           β”‚
β”‚  β”‚          β”‚           β”‚                           β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚                           β”‚
β”‚  β”‚  β”‚  useAuth()     β”‚  β”‚                           β”‚
β”‚  β”‚  β”‚  useUser()     β”‚  β”‚                           β”‚
β”‚  β”‚  β”‚  auth()        β”‚  β”‚  ← Server-side           β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚                           β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                           β”‚
β”‚             β”‚                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚              CLERK API                        β”‚    β”‚
β”‚  β”‚  β€’ Session Management                         β”‚    β”‚
β”‚  β”‚  β€’ JWT Tokens                                 β”‚    β”‚
β”‚  β”‚  β€’ User CRUD                                  β”‚    β”‚
β”‚  β”‚  β€’ OAuth Flows                                β”‚    β”‚
β”‚  β”‚  β€’ MFA Verification                           β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Setup dan Instalasi

Langkah 1: Buat Akun Clerk

Setup
1. Buka https://clerk.com dan daftar akun (bisa pakai GitHub)
2. Buat aplikasi baru di dashboard
3. Pilih metode autentikasi yang diinginkan:
   - Email / Password
   - Google Sign-In
   - GitHub Sign-In
   - Magic Link (passwordless)
4. Catat API Keys yang diberikan:
   - NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
   - CLERK_SECRET_KEY=sk_test_...

Langkah 2: Install Package

Bash
# Untuk Next.js App Router
npm install @clerk/nextjs

# Untuk React (standalone)
npm install @clerk/react

# Untuk Remix
npm install @clerk/remix

# Untuk Express/Node.js backend
npm install @clerk/express

# Untuk Astro
npm install @clerk/astro

Langkah 3: Environment Variables

.env.local
# .env.local β€” JANGAN commit ke git!
# Ambil dari Clerk Dashboard β†’ API Keys

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_aWQtZm9v...
CLERK_SECRET_KEY=sk_test_abc123xyz...

# URL configuration
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard

# Webhook secret (untuk verifikasi webhook)
CLERK_WEBHOOK_SECRET=whsec_...

Langkah 4: ClerkProvider

app/layout.tsx (Next.js App Router)
// app/layout.tsx β€” Root Layout dengan ClerkProvider
import { ClerkProvider } from '@clerk/nextjs';
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata = {
  title: 'BeebaneLabs',
  description: 'Tutorial web development modern'
};

export default function RootLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <ClerkProvider>
      <html lang="id">
        <body className={inter.className}>
          {children}
        </body>
      </html>
    </ClerkProvider>
  );
}
πŸ’‘ Tips

ClerkProvider harus membungkus seluruh aplikasi. Di Next.js App Router, taruh di app/layout.tsx. Di Pages Router, taruh di pages/_app.tsx. Pastikan environment variables diawali dengan NEXT_PUBLIC_ untuk yang bisa diakses di client-side.

3. Komponen UI Clerk

Clerk menyediakan komponen UI siap pakai yang bisa langsung digunakan. Komponen-komponen ini sudah mendukung dark mode, responsive, dan bisa dikustomisasi.

SignIn dan SignUp

app/sign-in/[[...sign-in]]/page.tsx
// app/sign-in/[[...sign-in]]/page.tsx
// Catch-all route untuk Clerk sign-in flow
import { SignIn } from '@clerk/nextjs';

export default function SignInPage() {
  return (
    <div style={{
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      minHeight: '100vh'
    }}>
      <SignIn
        path="/sign-in"
        routing="path"
        signUpUrl="/sign-up"
        appearance={{
          elements: {
            rootBox: 'mx-auto',
            card: 'shadow-xl rounded-2xl'
          }
        }}
      />
    </div>
  );
}

// app/sign-up/[[...sign-up]]/page.tsx
import { SignUp } from '@clerk/nextjs';

export default function SignUpPage() {
  return (
    <div style={{
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      minHeight: '100vh'
    }}>
      <SignUp
        path="/sign-up"
        routing="path"
        signInUrl="/sign-in"
      />
    </div>
  );
}

UserButton β€” Profil Dropdown

components/Header.tsx
// components/Header.tsx
import {
  SignedIn,
  SignedOut,
  SignInButton,
  SignUpButton,
  UserButton
} from '@clerk/nextjs';

export default function Header() {
  return (
    <header className="header">
      <nav>
        <a href="/">BeebaneLabs</a>

        <div className="nav-right">
          {/*
            SignedIn: hanya render saat user sudah login
            SignedOut: hanya render saat user belum login
          */}
          <SignedOut>
            <SignInButton mode="modal">
              <button className="btn-secondary">Masuk</button>
            </SignInButton>
            <SignUpButton mode="modal">
              <button className="btn-primary">Daftar</button>
            </SignUpButton>
          </SignedOut>

          <SignedIn>
            <UserButton
              afterSignOutUrl="/"
              appearance={{
                elements: {
                  avatarBox: 'w-10 h-10'
                }
              }}
            />
          </SignedIn>
        </div>
      </nav>
    </header>
  );
}

Semua Komponen UI Clerk

KomponenFungsi
<SignIn />Halaman login lengkap (email, social, passwordless)
<SignUp />Halaman registrasi lengkap
<UserButton />Avatar profil dengan dropdown menu
<UserProfile />Halaman pengaturan profil lengkap
<OrganizationSwitcher />Switch antar organization
<OrganizationList />Daftar organizations
<OrganizationProfile />Profil dan settings organization
<CreateOrganization />Form buat organization baru
<SignInButton />Tombol trigger sign-in (modal/redirect)
<SignUpButton />Tombol trigger sign-up
<SignOutButton />Tombol sign-out
<SignedIn />Wrapper β€” render hanya saat authenticated
<SignedOut />Wrapper β€” render hanya saat unauthenticated
<Protect />Render berdasarkan permission/role

4. User Management

Mengakses User Data di Client

components/UserProfile.tsx
// components/UserProfile.tsx
'use client';
import { useUser, useAuth } from '@clerk/nextjs';

export default function UserProfile() {
  const { user, isLoaded, isSignedIn } = useUser();
  const { userId, sessionId, getToken } = useAuth();

  // Loading state
  if (!isLoaded) {
    return <div>Loading...</div>;
  }

  // Not signed in
  if (!isSignedIn) {
    return <div>Silakan login terlebih dahulu</div>;
  }

  return (
    <div className="profile-card">
      {/* Avatar */}
      <img
        src={user.imageUrl}
        alt={user.fullName || 'User'}
        className="avatar"
      />

      {/* Informasi dasar */}
      <h2>{user.fullName}</h2>
      <p>{user.primaryEmailAddress?.emailAddress}</p>

      {/* Metadata */}
      <div className="meta">
        <span>ID: {user.id}</span>
        <span>Session: {sessionId}</span>
        <span>
          Bergabung: {user.createdAt?.toLocaleDateString('id-ID')}
        </span>
      </div>

      {/* Verifikasi email */}
      <div className="verifications">
        {user.emailAddresses.map((email) => (
          <div key={email.id}>
            {email.emailAddress}
            {email.verification?.status === 'verified'
              ? ' βœ…'
              : ' ❌ Belum verifikasi'}
          </div>
        ))}
      </div>

      {/* Public metadata */}
      <div className="metadata">
        <p>Role: {user.publicMetadata.role as string || 'user'}</p>
      </div>
    </div>
  );
}

Mengakses User Data di Server

app/dashboard/page.tsx
// app/dashboard/page.tsx β€” Server Component
import { auth, currentUser } from '@clerk/nextjs/server';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  // auth() β€” mendapatkan userId dan sessionId di server
  const { userId, sessionId } = auth();

  // Redirect jika belum login
  if (!userId) {
    redirect('/sign-in');
  }

  // currentUser() β€” mendapatkan data lengkap user
  const user = await currentUser();

  return (
    <div className="dashboard">
      <h1>Dashboard</h1>
      <p>Selamat datang, {user?.firstName}!</p>

      <div className="stats">
        <div className="stat">
          <span className="label">User ID</span>
          <span className="value">{userId}</span>
        </div>
        <div className="stat">
          <span className="label">Email</span>
          <span className="value">
            {user?.primaryEmailAddress?.emailAddress}
          </span>
        </div>
        <div className="stat">
          <span className="label">Role</span>
          <span className="value">
            {(user?.publicMetadata?.role as string) || 'user'}
          </span>
        </div>
      </div>
    </div>
  );
}

Updating User Metadata

app/api/set-role/route.ts
// app/api/set-role/route.ts
import { auth, clerkClient } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const { userId } = auth();

  if (!userId) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    );
  }

  const { role } = await request.json();

  // Update public metadata (bisa diakses di client)
  await clerkClient.users.updateUserMetadata(userId, {
    publicMetadata: {
      role: role,  // 'admin', 'editor', 'user'
    }
  });

  return NextResponse.json({ success: true, role });
}

// Tipe metadata:
// publicMetadata    β†’ bisa diakses di client (useUser)
// privateMetadata   β†’ hanya di server (clerkClient)
// unsafeMetadata    β†’ user bisa update sendiri

5. Middleware dan Proteksi Route

Clerk Middleware

middleware.ts
// middleware.ts β€” di root proyek (bukan di app/)
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

// Definisikan route yang membutuhkan autentikasi
const isProtectedRoute = createRouteMatcher([
  '/dashboard(.*)',     // Semua route di /dashboard
  '/settings(.*)',      // Semua route di /settings
  '/api/protected(.*)', // API routes yang dilindungi
]);

// Definisikan route yang hanya untuk admin
const isAdminRoute = createRouteMatcher([
  '/admin(.*)',
]);

export default clerkMiddleware((auth, req) => {
  // Proteksi route yang membutuhkan login
  if (isProtectedRoute(req)) {
    auth().protect();
  }

  // Proteksi route admin β€” butuh role 'admin'
  if (isAdminRoute(req)) {
    auth().protect({
      unauthorizedUrl: '/sign-in',
      forbiddenUrl: '/not-authorized'
    });
  }
});

export const config = {
  // Matcher: route mana yang diproses middleware
  matcher: [
    // Skip internal Next.js dan static files
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    // Selalu jalankan untuk API routes
    '/(api|trpc)(.*)',
  ],
};

Route Protection dengan Protect Component

app/admin/page.tsx
// app/admin/page.tsx β€” Protect berdasarkan role
import { Protect } from '@clerk/nextjs';

export default function AdminPage() {
  return (
    <div>
      <h1>Admin Panel</h1>

      {/*
        Protect component β€” render berdasarkan permission/role
        Jika user tidak punya role 'admin', tampilkan fallback
      */}
      <Protect
        role="admin"
        fallback={
          <div className="error">
            <h2>Akses Ditolak</h2>
            <p>Anda tidak memiliki izin untuk mengakses halaman ini.</p>
          </div>
        }
      >
        <div className="admin-content">
          <h2>Manajemen User</h2>
          <p>Konten khusus admin yang hanya bisa dilihat oleh admin.</p>
          {/* Admin content here */}
        </div>
      </Protect>
    </div>
  );
}

6. Webhooks

Clerk mengirim webhook events setiap kali terjadi perubahan pada user, organization, atau session. Ini sangat penting untuk sinkronisasi data dengan database Anda sendiri.

Setup Webhook di Clerk Dashboard

Setup
1. Buka Clerk Dashboard β†’ Webhooks
2. Klik "Add Endpoint"
3. Masukkan URL endpoint Anda:
   https://your-domain.com/api/webhooks/clerk
4. Pilih events yang ingin di-subscribe:
   βœ… user.created
   βœ… user.updated
   βœ… user.deleted
   βœ… organization.created
   βœ… organizationMembership.created
5. Simpan β€” catat "Signing Secret" (whsec_...)
6. Tambahkan ke .env.local:
   CLERK_WEBHOOK_SECRET=whsec_...

Webhook Handler

app/api/webhooks/clerk/route.ts
// app/api/webhooks/clerk/route.ts
import { Webhook } from 'svix';
import { headers } from 'next/headers';
import { WebhookEvent } from '@clerk/nextjs/server';
import { db } from '@/lib/db';

export async function POST(request: Request) {
  // 1. Verifikasi webhook signature
  const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
  if (!WEBHOOK_SECRET) {
    throw new Error('CLERK_WEBHOOK_SECRET not set');
  }

  const headerPayload = headers();
  const svixId = headerPayload.get('svix-id');
  const svixTimestamp = headerPayload.get('svix-timestamp');
  const svixSignature = headerPayload.get('svix-signature');

  if (!svixId || !svixTimestamp || !svixSignature) {
    return new Response('Missing svix headers', { status: 400 });
  }

  const payload = await request.json();
  const body = JSON.stringify(payload);

  const wh = new Webhook(WEBHOOK_SECRET);
  let evt: WebhookEvent;

  try {
    evt = wh.verify(body, {
      'svix-id': svixId,
      'svix-timestamp': svixTimestamp,
      'svix-signature': svixSignature,
    }) as WebhookEvent;
  } catch (err) {
    console.error('Webhook verification failed:', err);
    return new Response('Verification failed', { status: 400 });
  }

  // 2. Handle berbagai event types
  const eventType = evt.type;

  switch (eventType) {
    case 'user.created': {
      const { id, email_addresses, first_name, last_name, image_url } = evt.data;
      console.log('User created:', id);

      // Simpan user ke database lokal
      await db.user.create({
        data: {
          clerkId: id,
          email: email_addresses[0]?.email_address || '',
          name: `${first_name || ''} ${last_name || ''}`.trim(),
          avatar: image_url,
          createdAt: new Date()
        }
      });
      break;
    }

    case 'user.updated': {
      const { id, email_addresses, first_name, last_name, image_url } = evt.data;
      console.log('User updated:', id);

      await db.user.update({
        where: { clerkId: id },
        data: {
          email: email_addresses[0]?.email_address || '',
          name: `${first_name || ''} ${last_name || ''}`.trim(),
          avatar: image_url,
        }
      });
      break;
    }

    case 'user.deleted': {
      const { id } = evt.data;
      console.log('User deleted:', id);

      await db.user.delete({
        where: { clerkId: id! }
      });
      break;
    }

    default:
      console.log('Unhandled event type:', eventType);
  }

  return new Response('Webhook processed', { status: 200 });
}

Webhook Events yang Tersedia

EventTrigger
user.createdUser baru mendaftar
user.updatedUser mengupdate profil
user.deletedUser dihapus dari Clerk
session.createdUser login (session baru)
session.endedUser logout
organization.createdOrganization baru dibuat
organization.updatedOrganization diupdate
organizationMembership.createdUser ditambahkan ke org
organizationMembership.deletedUser dihapus dari org
⚠️ Keamanan Webhook

Selalu verifikasi signature webhook menggunakan svix library! Tanpa verifikasi, siapapun bisa mengirim fake webhook ke endpoint Anda. Tanda tangan digital memastikan webhook benar-benar berasal dari Clerk.

7. Organizations

Organizations memungkinkan Anda membuat fitur multi-tenancy β€” satu user bisa menjadi anggota beberapa organization, masing-masing dengan role dan permission berbeda.

Organization Components

app/organizations/page.tsx
// app/organizations/page.tsx
import {
  OrganizationSwitcher,
  OrganizationList,
  CreateOrganization
} from '@clerk/nextjs';

export default function OrganizationsPage() {
  return (
    <div className="org-page">
      <h1>Organizations</h1>

      {/*
        OrganizationSwitcher β€” dropdown untuk switch org
        Menampilkan org saat ini dan memungkinkan switch
      */}
      <OrganizationSwitcher
        afterCreateOrganizationUrl="/organization/:slug"
        afterSelectOrganizationUrl="/organization/:slug"
        appearance={{
          elements: {
            rootBox: 'mb-8'
          }
        }}
      />

      {/*
        OrganizationList β€” daftar semua org yang user ikuti
        Plus tombol untuk membuat org baru
      */}
      <OrganizationList
        hidePersonal={false}
        afterSelectOrganizationUrl="/organization/:slug"
        afterCreateOrganizationUrl="/organization/:slug"
      />
    </div>
  );
}

Organization Roles dan Permissions

app/api/organizations/[orgId]/route.ts
// app/api/organizations/[orgId]/route.ts
import { auth, clerkClient } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';

export async function GET(
  request: Request,
  { params }: { params: { orgId: string } }
) {
  const { userId, orgId } = auth();

  if (!userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // Mendapatkan data organization
  const organization = await clerkClient.organizations.getOrganization({
    organizationId: params.orgId
  });

  // Mendapatkan daftar member
  const members = await clerkClient.organizations.getOrganizationMembershipList({
    organizationId: params.orgId
  });

  return NextResponse.json({
    organization,
    members: members.data.map(m => ({
      id: m.id,
      role: m.role,
      user: {
        id: m.publicUserData?.userId,
        name: m.publicUserData?.firstName + ' ' + m.publicUserData?.lastName,
        avatar: m.publicUserData?.imageUrl
      }
    }))
  });
}

8. Kustomisasi dan Theming

Appearance API

app/layout.tsx β€” Kustomisasi Global
// app/layout.tsx β€” Kustomisasi global Clerk
import { ClerkProvider } from '@clerk/nextjs';

// Definisikan tema global
const clerkAppearance = {
  variables: {
    // Warna
    colorPrimary: '#3b82f6',
    colorBackground: '#0f172a',
    colorInputBackground: '#1e293b',
    colorInputText: '#e2e8f0',
    colorText: '#e2e8f0',
    colorTextSecondary: '#94a3b8',
    colorDanger: '#ef4444',
    colorSuccess: '#22c55e',

    // Typography
    fontFamily: 'Inter, system-ui, sans-serif',
    fontSize: '0.875rem',
    fontWeight: {
      normal: 400,
      medium: 500,
      bold: 700
    },

    // Border
    borderRadius: '0.75rem',

    // Spacing
    spacingUnit: '1rem'
  },
  elements: {
    // Card styling
    card: {
      boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)',
      border: '1px solid rgba(255, 255, 255, 0.1)',
    },

    // Button styling
    formButtonPrimary: {
      backgroundColor: '#3b82f6',
      '&:hover': {
        backgroundColor: '#2563eb'
      }
    },

    // Input styling
    formFieldInput: {
      border: '1px solid rgba(255, 255, 255, 0.2)',
      '&:focus': {
        borderColor: '#3b82f6',
        boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.3)'
      }
    },

    // Avatar
    avatarBox: {
      width: '3rem',
      height: '3rem'
    },

    // Footer
    footerActionLink: {
      color: '#3b82f6',
      '&:hover': {
        color: '#60a5fa'
      }
    }
  }
};

export default function RootLayout({
  children
}: {
  children: React.ReactNode
}) {
  return (
    <ClerkProvider appearance={clerkAppearance}>
      <html lang="id">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  );
}

Path Aliases

tsconfig.json + next.config.js
// tsconfig.json β€” TypeScript path aliases
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@utils/*": ["./src/utils/*"],
      "@hooks/*": ["./src/hooks/*"],
      "@styles/*": ["./src/styles/*"],
      "@types/*": ["./src/types/*"]
    }
  }
}

// next.config.js β€” Turbopack aliases (opsional)
// Turbopack bisa membaca langsung dari tsconfig.json
module.exports = {
  turbopack: {
    resolveAlias: {
      // Tambahkan alias yang tidak ada di tsconfig
      '@lib': './src/lib',
      '@config': './config'
    }
  }
};

// Penggunaan di kode:
import Button from '@components/Button';
import { formatRupiah } from '@utils/format';
import useAuth from '@hooks/useAuth';

8. CSS dan Asset Handling

CSS Modules

components/Button.module.css
/* components/Button.module.css */
/* CSS Modules β€” class names di-scope secara otomatis */
.button {
  padding: 0.75rem 1.5rem;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;
}

.primary {
  background: #3b82f6;
  color: white;
  border: none;
}

.primary:hover {
  background: #2563eb;
  transform: translateY(-1px);
}

.secondary {
  background: transparent;
  color: #3b82f6;
  border: 2px solid #3b82f6;
}

.secondary:hover {
  background: #3b82f6;
  color: white;
}

/* Output: class names menjadi unik */
/* .Button_button__x7d2k { ... } */
/* .Button_primary__a3f9m { ... } */
components/Button.tsx
// components/Button.tsx
import styles from './Button.module.css';

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  children: React.ReactNode;
  onClick?: () => void;
}

export default function Button({
  variant = 'primary',
  children,
  onClick
}: ButtonProps) {
  return (
    <button
      className={`${styles.button} ${styles[variant]}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// Penggunaan:
// <Button variant="primary">Simpan</Button>
// <Button variant="secondary">Batal</Button>

9. Komparasi dengan Bundler Lain

Turbopack vs Webpack vs Vite vs esbuild

AspekTurbopackWebpackViteesbuild
BahasaRustJavaScriptJS + esbuildGo
Dev Speed⚑ Tercepat🐌 LambatπŸš€ CepatπŸš€ Cepat
HMR⚑ <100ms1-10s50-200msN/A (no HMR)
Prod Build🟑 Berkembangβœ… Matureβœ… (Rollup)⚑ Tercepat
Code Splittingβœ… Automaticβœ… Manual/Optimalβœ… (Rollup)🟑 Basic
CSS Supportβœ… Fullβœ… Fullβœ… Full🟑 Basic
Ekosistem🟑 Growingβœ… Massiveβœ… Large🟑 Growing
FrameworkNext.jsSemuaSemuaSemua
Best ForNext.js appsLegacy/LargeModern SPALibraries

Kapan Harus Menggunakan Turbopack?

Decision Guide
βœ… GUNAKAN Turbopack jika:
   - Anda menggunakan Next.js 15+
   - Proyek sangat besar (10.000+ files)
   - HMR lambat dengan bundler saat ini
   - Development speed adalah prioritas utama
   - Proyek baru yang belum punya custom build config

❌ JANGAN GUNAKAN Turbopack jika:
   - Menggunakan framework selain Next.js (misal: Nuxt, SvelteKit)
   - Membutuhkan plugin Webpack yang kompleks
   - Membutuhkan production build optimization (belum matang)
   - Proyek sangat bergantung pada custom webpack config
   - Stabilitas lebih penting daripada speed (gunakan Webpack/Vite)

10. Masa Depan Turbopack

Turbopack masih dalam pengembangan aktif. Berikut roadmap yang diumumkan oleh tim Vercel:

Roadmap Turbopack

Roadmap
2024 (SELESAI):
βœ… Turbopack Dev β€” stable di Next.js 15
βœ… CSS Modules, PostCSS, Tailwind support
βœ… HMR yang sangat cepat
βœ… Server Components support

2025-2026 (DALAM PENGEMBANGAN):
πŸ”„ Turbopack Build β€” production bundling
πŸ”„ Improved caching (persistent disk cache)
πŸ”„ Module Federation support
πŸ”„ Broader framework support (beyond Next.js)

FUTURE (PLANNED):
πŸ“‹ Turbopack as standalone tool
πŸ“‹ Custom plugin API
πŸ“‹ Better bundle analysis tools
πŸ“‹ Parallel compilation improvements
πŸ“‹ Support untuk library bundling

GOAL AKHIR:
β†’ Turbopack menjadi bundler default untuk semua proyek
β†’ Menggantikan Webpack di ekosistem JavaScript
β†’ Menggabungkan kecepatan esbuild + fitur Webpack
πŸ’‘ Tips

Saat ini Turbopack paling optimal digunakan untuk development di Next.js. Untuk production build, Next.js masih menggunakan Webpack/Rollup secara default. Namun seiring Turbopack Build stabil, ini akan berubah.

11. Quiz Pemahaman

1. Bahasa apa yang digunakan untuk membangun Turbopack?

2. Apa konsep kunci yang membuat Turbopack sangat cepat?

3. Bagaimana cara mengaktifkan Turbopack di Next.js?

4. Siapa yang mengembangkan Turbopack?

5. Apa status Turbopack untuk production build saat ini?

πŸ“ Ringkasan

Dalam tutorial ini, Anda telah mempelajari:

  • Pengenalan Turbopack dan motivasi pembuatannya
  • Arsitektur incremental computation dengan Turbo Engine
  • Performa dan benchmark dibanding Webpack dan Vite
  • Integrasi dengan Next.js dan cara mengaktifkannya
  • Konfigurasi loaders, aliases, dan environment variables
  • Migrasi dari Webpack ke Turbopack
  • CSS Modules dan asset handling
  • Komparasi dengan bundler lain
  • Roadmap dan masa depan Turbopack