Next.js Local Environment Setup

seedlingLast update on Jul 18, 2026
Download .md

Ringkasan

Setup ini diambil dari dua project Next.js production:

  • soalku — platform ujian online dengan multi-role (superadmin, school, student), Drizzle ORM + Better Auth
  • edusense — dashboard analitik pendidikan dengan Prisma + Better Auth + recharts

Keduanya menggunakan Next.js App Router, TypeScript strict, shadcn/ui, Tailwind v4, dan Better Auth.


Tools yang Dipakai

ToolFungsiInstalasi
BunRuntime + package manager (utama)brew install bun
ESLintLinting (flat config)bun add -D eslint
PrettierCode formattingbun add -D prettier
LefthookGit hooks managerbrew install lefthook
PlaywrightE2E testingbun add -D @playwright/test
shadcn/uiComponent library (copy-paste)bunx shadcn@latest init
Better AuthAuthentication (type-safe)bun add better-auth
Drizzle / PrismaORMbun add drizzle-orm / bun add prisma
DockerContainer runtimebrew install --cask docker

1. Package Manager & Scripts

Dua project sama-sama bisa pakai Bun. Script dari soalku:

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint",
    "format": "prettier --write --ignore-unknown .",
    "format:check": "prettier --check --ignore-unknown .",
    "commitlint": "commitlint --edit",
    "db:generate": "drizzle-kit generate",
    "db:push": "drizzle-kit push",
    "db:studio": "drizzle-kit studio",
    "db:seed": "bun scripts/seed.ts",
    "test": "playwright test",
    "test:ui": "playwright test --ui",
    "preinstall": "npx only-allow bun"
  }
}

Perintah Sehari-hari

bun dev          # Dev server (port 3000)
bun build        # Production build
bun lint         # ESLint
bun format       # Prettier
bun test         # Playwright E2E
bun db:generate  # Generate Drizzle migration
bun db:push      # Push schema ke database
bun db:studio    # Drizzle Studio (GUI)

2. ESLint + Prettier

ESLint Flat Config

// eslint.config.mjs
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import { defineConfig, globalIgnores } from "eslint/config";

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
  {
    rules: {
      "react/no-children-prop": "off",
    },
  },
]);

export default eslintConfig;

Prettier

// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all"
}

3. TypeScript Config

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts",
    ".next/dev/types/**/*.ts",
    "**/*.mts"
  ],
  "exclude": ["node_modules"]
}

4. Lefthook (Git Hooks)

# lefthook.yml
pre-commit:
  commands:
    prettier:
      glob: "*.{js,mjs,cjs,ts,tsx,mts,cts,json,md,css,yml,yaml}"
      run: bun run format {staged_files}
      stage_fixed: true
    eslint:
      glob: "*.{js,mjs,cjs,ts,tsx,mts,cts}"
      run: npx eslint --fix {staged_files}
      stage_fixed: true
    typecheck:
      glob: "*.{ts,tsx,mts,cts}"
      run: npx tsc --noEmit

commit-msg:
  commands:
    commitlint:
      run: npx commitlint --edit {1}

Install:

lefthook install

5. Database

Opsi A: Drizzle ORM (soalku)

// drizzle.config.ts
import type { Config } from "drizzle-kit";

export default {
  schema: "./core/db/schema.ts",
  out: "./core/db/migrations",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
} satisfies Config;

Opsi B: Prisma (edusense)

// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

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

Docker Compose (PostgreSQL)

# compose.yml
services:
  db:
    image: postgres:18-alpine
    restart: unless-stopped
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: myapp
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql

volumes:
  pgdata:

6. Authentication (Better Auth)

Better Auth digunakan di kedua project. Setup minimal:

// lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/core/db";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg", // atau 'mysql'
  }),
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
});

Generate schema:

bun db:auth-generate
# better-auth generate --output lib/db/auth-schema.ts --config lib/auth.ts

7. shadcn/ui Setup

# Init (sekali)
bunx shadcn@latest init

# Add components
bunx shadcn@latest add button card dialog dropdown-menu form input select table tabs

# Search
bunx shadcn@latest search @shadcn -q "sidebar"

Struktur komponen:

  • components
    • ui/ # shadcn/ui primitives
      • button.tsx
      • card.tsx
      • dialog.tsx
      • form.tsx
      • input.tsx
    • shared/ # Shared components (header, footer, sidebar)
    • data-table/ # Data table components

8. Testing (Playwright)

// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  fullyParallel: false,
  retries: 1,
  workers: 1,
  reporter: "list",
  use: {
    baseURL: "http://localhost:3000",
    trace: "on-first-retry",
  },
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
  ],
  webServer: {
    command: "bun run dev",
    url: "http://localhost:3000",
    reuseExistingServer: !process.env.CI,
    timeout: 30000,
  },
});

9. Project Structure

  • project
    • app/ # Next.js App Router
      • (auth)/ # Route group: login, register
      • (superadmin)/ # Route group: dashboard admin
      • api/ # Route handlers
      • layout.tsx
      • page.tsx
    • components/ # Reusable UI components
      • ui/ # shadcn/ui
      • shared/ # Header, footer, sidebar
    • core/ # Business logic (soalku pattern)
      • db/ # Drizzle schema + migrations
    • lib/ # Utils, auth config, validators
    • hooks/ # Custom hooks
    • modules/ # Feature modules
    • config/ # App config constants
    • tests/ # E2E tests
    • public/ # Static assets
    • next.config.ts
    • drizzle.config.ts
    • lefthook.yml
    • eslint.config.mjs
    • tsconfig.json
    • compose.yml

10. Middleware (Auth Guard)

// middleware.ts (root project)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("token");

  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*"],
};

Quick Start (Proyek Baru)

# 1. Create project
bun create next-app@latest my-app
cd my-app

# 2. Install core dependencies
bun add better-auth drizzle-orm
bun add -D drizzle-kit @playwright/test lefthook

# 3. Init shadcn/ui
bunx shadcn@latest init
bunx shadcn@latest add button card dialog form input table

# 4. Setup docker
# Copy compose.yml dari atas → sesuaikan env

# 5. Setup git hooks
lefthook install

# 6. Generate auth schema
bun db:auth-generate

# 7. Start dev
docker compose up -d
bun dev

Perbandingan Dua Project

Aspeksoalkuedusense
Package managerBunnpm
ORMDrizzlePrisma
DBPostgreSQLMariaDB
AuthBetter Auth (email + social)Better Auth
UIshadcn/ui + Tailwind v4shadcn/ui + Tailwind v4 + radix-ui
TestingPlaywrightN/A
LintingESLint flat configESLint flat config
Git hooksLefthook + commitlintN/A
Next.js15.x16.x
ChartsN/Arecharts
Formsshadcn/ui formreact-hook-form + zod