Modern stack kurulumu — Next.js 16, TypeScript, Tailwind v4
Next.js 16 App Router, TypeScript, Tailwind v4 ve shadcn/ui ile production-ready bir proje iskelesini kurarız. Folder structure, env discipline ve code style discipline gibi "ileride pişman olmayacağın" kararları başta veririz.
Yeni bir Next.js projesi başlatmak pnpm create next-app ile 5 dakikalık
iş. Ama ilk haftadan sonra fark edersin: dosyalar dağılmış, env'ler
karışmış, lint kuralları yarım, deploy zamanı her şey patlıyor.
Bu bölümde projeyi sıfırdan kuracağız ama "production'a giderken yarım yola dönmek istemeyeceğin" kararları önden alacağız. Sonraki 7 bölümün tüm temeli bu bölümün üstüne inşa olacak.
Hangi sürümlerle çalışıyoruz?
Bu eğitim Next.js 16, React 19, TypeScript 5, Tailwind v4 üzerine yazıldı. Sürüm numaraları önemli — Next.js 13+ App Router, 14'te server actions kararlı, 15'te async params/searchParams, 16'da daha hızlı build
- daha iyi caching. Eğitim boyunca bu sürümlere özgü pattern'leri kullanacağız.
node --version # ≥ 20.0.0 olmalı
pnpm --version # ≥ 8.0.0 olmalınpm veya yarn da çalışır ama bu eğitimde pnpm kullanacağız —
disk + hız avantajı, monorepo desteği daha iyi.
Projeyi başlatma
pnpm create next-app@latest myapp --typescript --tailwind --app \
--eslint --use-pnpm --src-dir=false --import-alias="@/*"
cd myappFlag'ler ne yapıyor?
--typescript— TS başlat (zaten kararımız bu)--tailwind— Tailwind v4 + PostCSS hazır--app— App Router (Pages Router'ı geçtik, modern Next.js bu)--eslint— ESLint config + flat config--src-dir=false—src/klasörü kullanmayalım, root'tan import daha temiz--import-alias="@/*"—@/components/foogibi import'lar için baseUrl
Kurulum sonrası pnpm dev ile localhost:3000'de "Welcome to Next.js"
sayfası açılır. Bu noktada sadece "next-app boilerplate" var — şimdi
production-ready hale getireceğiz.
Folder structure — ne nereye?
Boilerplate'in default'u tek bir app/ klasörü. Gerçek projelerde dosya
sayısı arttıkça mantıksal gruplar lazım. Önerdiğim yapı:
myapp/
├── app/ # Next.js routes (page.tsx, layout.tsx, route.ts)
│ ├── (main)/ # Layout group — public sayfalar
│ ├── (auth)/ # Layout group — auth sayfaları (signin/signup)
│ ├── api/ # API routes (webhook, sse, OAuth)
│ └── layout.tsx # Root layout
├── components/ # Reusable UI components
│ ├── ui/ # shadcn/ui primitives (Button, Dialog, vs.)
│ ├── layout/ # Header, Footer, Sidebar
│ └── feature/ # Domain-specific (UserCard, OrderRow)
├── lib/ # Server-side utilities
│ ├── actions/ # Server actions (form handlers)
│ ├── queries/ # DB query helpers (read-only, type-safe)
│ ├── auth.ts # Auth instance export
│ └── utils.ts # cn(), formatDate(), vb.
├── db/ # Drizzle schema + client + migrations
│ ├── schema.ts
│ ├── client.ts
│ └── migrations/
├── content/ # MDX/Markdown content (eğer varsa)
├── public/ # Statik dosyalar
└── ...Neden bu yapı?
app/sadece route'lar — page, layout, route handler. Component logic'i buraya yazma; route'tan import et.components/ui/shadcn'in primitives'i. Dokunma, sürüm güncellemelerini manuel yap. (shadcnnpx shadcn add buttonile bu klasöre kopyalar.)lib/actions/velib/queries/ayrımı kritik: action'lar yazar, queries okur. Server actions formdan tetiklenir, DB'ye INSERT/UPDATE yapar. Queries sayfa render'ında DB'den okur. Karıştırma — action içinden query çağırabilirsin ama query içinden action çağrılmaz.db/sadece şema + client. İçerikle ilgili logiclib/queries/'de.
pnpm dev çalıştırırken bu klasörleri elle yarat:
mkdir -p components/ui components/layout components/feature \
lib/actions lib/queries dbCode style discipline — başta sıkı, sonra rahat
Production projede format + lint disiplini baştan sıkı kurulmalı. 3 ay sonra kodu okurken "ben mi yazdım bunu" deme — Prettier + ESLint
- TypeScript strict mode bunu garanti eder.
Prettier
pnpm add -D prettier.prettierrc (root):
{
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 80,
"plugins": ["prettier-plugin-tailwindcss"]
}prettier-plugin-tailwindcss Tailwind class'larını otomatik sıralar
(flex items-center gap-2 p-4 gibi tutarlı sıra). Manuel yazıma sırasına
güvenme — plugin yapsın.
pnpm add -D prettier-plugin-tailwindcssESLint — Next.js + TypeScript strict
Boilerplate'in eslint.config.mjs'ini biraz sıkılaştıralım:
import next from "eslint-config-next";
export default [
...next,
{
rules: {
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-explicit-any": "error",
"react-hooks/exhaustive-deps": "error",
"import/order": [
"error",
{
groups: ["builtin", "external", "internal", "parent", "sibling"],
"newlines-between": "always",
},
],
},
},
];no-explicit-any özellikle önemli — any kullanmak type system'in
sebebini ortadan kaldırır. Lazımsa unknown + type narrowing yap.
TypeScript strict
tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
// ... diğer default'lar
}
}noUncheckedIndexedAccess runtime hata kaynağını kapatır:
const arr = [1, 2, 3];
const x = arr[5]; // x: number (yanlış — undefined olabilir!)
// strict + noUncheckedIndexedAccess ile:
// x: number | undefined ✓Junior'ın en çok düştüğü hata burada — TS sana yanlış güven veriyor. Flag açınca compiler tüm "sınır dışı erişim" potansiyelini işaretler.
Environment variables — .env discipline
Production'da secret leak'lerinin %80'i env discipline'ı zayıf projelerden çıkar. Bu konuyu baştan oturtalım.
4 dosya pattern'i
.env # Default değerler — git'e commit edilebilir (örn PORT=3000)
.env.local # Lokal secret'lar — GIT'e ASLA commit etme
.env.example # Template — git'e commit, gerçek değer yok
.env.production # Prod-only override — git'e commit etme.gitignore zaten .env* ignore ediyor, sadece .env*.example exception:
.env*
!.env*.exampleConvention'lar
- Server-side secret → prefixsiz:
DATABASE_URL,BETTER_AUTH_SECRET - Client-side public →
NEXT_PUBLIC_*:NEXT_PUBLIC_SITE_URL - NEXT_PUBLIC_ ile başlayan her şey bundle'a embed edilir — secret buraya koyma, lobi'de duyurmuş gibi olur.
Type-safe env — Zod + parse
Env'lerin runtime'da tek noktadan validate edilmesi en sağlıklısı:
// lib/env.ts
import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url(),
BETTER_AUTH_SECRET: z.string().min(32),
GITHUB_CLIENT_ID: z.string().optional(),
GITHUB_CLIENT_SECRET: z.string().optional(),
RESEND_API_KEY: z.string().optional(),
NEXT_PUBLIC_SITE_URL: z.string().url(),
});
export const env = envSchema.parse(process.env);lib/env.ts'i import ettiğin anda env validate edilir. Eksik bir
DATABASE_URL ile uygulama açılmadan crash eder — production'da yanlış
env'le 30 dakika debugging yerine 5 saniyede patlayan açık hata.
pnpm add zod kurulumu sonrası bu modülü import ettiğin her yerde
type-safe env access:
import { env } from "@/lib/env";
console.log(env.DATABASE_URL); // string ✓
console.log(env.UNKNOWN); // type error ✓shadcn/ui kurulumu
UI primitive'leri için shadcn/ui öneririm. Headless component library değil — kendi component'lerini sana kopyalar. Bu sayede tema değiştirebilir, custom feature ekleyebilir, üst sürüme geçtiğinde breaking change yaşamazsın.
pnpm dlx shadcn@latest initInit sırasında:
- Style → Default
- Base color → Zinc (sade) veya kendi rengin
- CSS variables → Yes (Tailwind v4 ile uyumlu)
İlk component'leri ekle:
pnpm dlx shadcn@latest add button input dialog dropdown-menuBunlar components/ui/ altına gelir. Sonradan istediğin kadar customize
edersin — Button'a yeni variant eklemek için doğrudan dosyayı düzenle.
İlk component'i yazalım
Az önce kurduğumuz Button'u kullanan basit bir component:
// components/feature/welcome-card.tsx
import { Button } from "@/components/ui/button";
export function WelcomeCard({ name }: { name: string }) {
return (
<div className="rounded-2xl border border-border bg-card p-6">
<h2 className="text-xl font-semibold tracking-tight">
Hoş geldin, {name}
</h2>
<p className="mt-2 text-sm text-muted-foreground">
Hazır olduğunda başlayalım.
</p>
<Button className="mt-4">Devam et</Button>
</div>
);
}Önemli noktalar:
import { Button } from "@/components/ui/button"—@/alias'ı baseUrl'den (./) gelir, dosya derinliği fark etmeztext-muted-foreground— shadcn'in CSS variable'ı (theme-aware)tracking-tight— Tailwind'in letter-spacing utility'si- Function name PascalCase, file name kebab-case — Next.js convention
Bu bölümün özeti
Bu bölümde:
- Next.js 16 + TS + Tailwind v4 + shadcn projesini kurduk
- Folder structure'ını scale-friendly kurguladık
- Code style (Prettier + ESLint + TS strict) discipline'i baştan oturttuk
- Env discipline + Zod-validated
lib/env.tsile type-safe env access kurduk - shadcn/ui ile UI primitive'leri ekledik
Sonraki bölümde Postgres + Drizzle ORM ile type-safe database kuracağız. Schema design, migration workflow, indexler ve N+1 önleme — junior'ın genelde sonraki yıl öğrendiği şeyleri şimdi kuracağız ki ileride refactor zorunluluğu olmasın.
Sor, birlikte çözelim.
community.czay.dev — Türkçe yazılım topluluğumuzda eğitimde takıldığın konuları sorabilir, başka geliştiricilerin deneyimlerinden faydalanabilirsin. Hızlı cevap, doğru yer.