ViraStack
  • Akademi
  • Hakkında
  • Destek
Genel BakışKaynaklarKatkı
ViraStack

Frontend ekosistemi: ücretsiz rehberler, proje şablonları, AI kuralları ve form bileşenleri.

Ürünler

  • ViraStack Start
  • ViraStack AI
  • ViraStack Mask
  • ViraStack Password
  • ViraStack Guide

Keşfet

  • Akademi
  • Hakkında
  • Marka
  • Destek

© 2026 ViraStack.

Açık kaynak · MIT

Mimari ve Ölçeklenebilirlik

03 / 09Proje

Proje Yapısını Düzenleme

Dağınık bir e-ticaret projesini feature-based yapıya taşıma.

Senaryo

Elinizde küçük bir mağaza var. Ürünler listeleniyor, sepete ekleniyor, detay sayfası açılıyor. Uygulama çalışıyor ama yapısı dağınık:

SorunNerede
Dosyalar türe göre dizilmişcomponents/, hooks/, utils/, types/
Genel dosya adlarıutils/utils.ts, types/types.ts
PascalCase dosya adlarıProductCard.tsx, useCart.ts
../../../ import'larıapp/products/[slug]/page.tsx
export * barrel'ı ve döngücomponents/index.ts ile hooks/useCart.ts
Ürün kartı sepeti biliyorProductCard içinde AddToCartButton

Göreviniz bu projeyi Klasör Yapısı ve İsimlendirme ile Component ve Kod Organizasyonu derslerindeki yapıya taşımak. Tek kural var: kullanıcı hiçbir farkı görmemeli.

Başlangıç ve çözüm GitHub'da: examples/architecture/restructure-project. starter/ klasöründen başlayın, takıldığınızda solution/ ile karşılaştırın.

İpucu

Proje next@16.4.2, eslint@9.39.5 ve @playwright/test@1.64.1 ile yazıldı. Testler Chromium'da production build üzerinde çalıştırıldı.

Adım 1: Önce Testi Çalıştırın

Martin Fowler'ın tanımıyla refactoring, kodun iç yapısını gözlemlenebilir davranışını değiştirmeden düzenlemektir.1 Davranışın değişmediğini bilmenin yolu bir testtir.

Projede bir Playwright testi hazır. Ürünleri sepete ekliyor ve başlıktaki özeti kontrol ediyor:

// e2e/cart.spec.ts
await page.getByRole("button", { name: "Mekanik Klavye sepete ekle" }).click();
await page.getByRole("button", { name: "Kablosuz Mouse sepete ekle" }).click();
await expect(summary).toHaveText("Sepet: 2 ürün, ₺3.398,00");
$ cd starter
npm install
npx playwright install chromium
npm run test:e2e

İki test geçmeli. Test dosya yollarını, bileşen adlarını ya da state'i bilmiyor; yalnızca kullanıcının gördüğüne bakıyor. Kent C. Dodds, iç ayrıntıya bağlı testlerin refactoring sırasında kod sağlamken kırıldığını yazar.2 Bu test ise taşıma boyunca hiç değişmeyecek.

Adım 2: Kuralları Ekleyin

Kuralları en başta açın. Lint hataları yapılacak işlerin listesi olur.

// eslint.config.mjs
{
  files: ["src/**/*.{ts,tsx}"],
  rules: {
    "no-restricted-imports": ["error", {
      patterns: [{ regex: "^\\.\\./", message: "Use the `@/` alias instead of `../`." }],
    }],
    "import/no-cycle": "error",
    "import/no-restricted-paths": ["error", {
      zones: [
        { target: "./src/features/cart", from: "./src/features", except: ["./cart"] },
        { target: "./src/features/products", from: "./src/features", except: ["./products"] },
        { target: "./src/features", from: "./src/app" },
        { target: ["./src/components", "./src/helpers"], from: ["./src/features", "./src/app"] },
      ],
    }],
  },
},

import eklentisini eslint-config-next zaten kaydediyor, ayrıca kurmanız gerekmez.

  • no-restricted-imports yalnızca ../ ile başlayan import'ları yasaklar.3

  • import/no-cycle döngüsel import'u bulur.4

  • import/no-restricted-paths özelliklerin birbirini import etmesini ve akışın tersine dönmesini engeller.5

Uyarı

../ yasağı için import/no-relative-parent-imports kuralını kullanmayın. Kural çözümlenen dosya yoluna bakıyor. Denemede @/components/ui/button gibi alias'lı import'ları da hata saydı.

$ pnpm lint
src/components/CartSummary.tsx
  3:1  error  '../hooks/useCart' import is restricted from being used by a pattern. Use the `@/` alias instead of `../`  no-restricted-imports
  3:1  error  Dependency cycle via ../components:3  import/no-cycle

✖ 22 problems (22 errors, 0 warnings)

Zone hataları henüz çıkmıyor, çünkü features/ klasörü yok. Onlar taşıma sırasında görünecek.

Adım 3: Ortak Kodu Ayırın

utils/utils.ts üç farklı işi bir arada tutuyor. Her fonksiyonu kullanıldığı yere taşıyın:

FonksiyonYeni yeriNeden
formatPricehelpers/format-price.tsHem ürün hem sepet kullanıyor
getCartTotalfeatures/cart/helpers/cart-total.tsYalnızca sepet kullanıyor
getProductBySlugfeatures/products/data/products.tsÜrün verisiyle birlikte durmalı

Button de iki özelliğin ortak parçası; components/ui/button.tsx'e gider. types/types.ts ikiye bölünür: Product ürüne, CartItem sepete.

Ortak katmana yalnızca iki özelliğin gerçekten paylaştığı kod çıkar.6 Gerisi özelliğin içinde kalır; Kent C. Dodds'ın dediği gibi kod, ilgili olduğu yere yakın durur.7

Adım 4: Özellikleri Taşıyın

src/
├── app/
├── components/ui/button.tsx
├── helpers/format-price.ts
└── features/
    ├── products/
    │   ├── components/product-card.tsx
    │   ├── data/products.ts
    │   ├── types/product.ts
    │   └── index.ts
    └── cart/
        ├── components/{cart-provider,cart-summary,add-to-cart-button}.tsx
        ├── helpers/cart-total.ts
        ├── types/cart-item.ts
        └── index.ts

Dosya adlarını kebab-case yapın. Linux'ta çalışan CI büyük/küçük harfi ayırır; tek bir yazım seçmek canlıda "Module not found" sürprizini önler.8 Import'ları @/ ile yazın; create-next-app bu alias'ı kendisi kurar.9

Adım 5: Döngüyü Kırın

Başlangıçta döngü şöyle kuruluyor: CartSummary → hooks/useCart → components/index.ts → CartSummary. export * barrel'ı her şeyi her şeye bağlıyor.

İki değişiklik yeter:

  1. useCart'ı cart-provider.tsx'e taşıyın. Context ve hook aynı dosyada durur.

  2. Özelliğin kökündeki index.ts'i ince tutun: yalnızca dışarının kullandığı parçalar, adlandırılmış re-export ile.10

// features/cart/index.ts
export { AddToCartButton } from "./components/add-to-cart-button";
export { CartProvider } from "./components/cart-provider";
export { CartSummary } from "./components/cart-summary";
export type { CartItem } from "./types/cart-item";

Özelliğin kendi dosyaları birbirini index.ts üzerinden değil, doğrudan dosya yoluyla import eder. TkDodo, barrel'ların döngüsel import'a ne kadar kolay yol açtığını anlatır.11

Adım 6: Özellikleri Ayırın

ProductCard dosyasını features/products'a taşıdığınızda lint şunu söyler:

src/features/products/components/product-card.tsx
  2:33  error  Unexpected path "@/features/cart" imported in restricted zone  import/no-restricted-paths

Ürün kartı sepeti bilmemeli. Butonu kartın dışından, bir prop ile verin:

// features/products/components/product-card.tsx
export function ProductCard({ product, action }: { product: Product; action?: React.ReactNode }) {
  return (
    <article className="card">
      <h2><Link href={`/products/${product.slug}`}>{product.name}</Link></h2>
      <p>{formatPrice(product.price)}</p>
      {action}
    </article>
  );
}
// app/page.tsx
<ProductCard product={product} action={<AddToCartButton {...product} />} />

İki özelliği birleştirmek sayfanın işi. Akış tek yönlü kalır: ortak kod → özellikler → uygulama.6

Adım 7: Son Kontrol

$ pnpm lint && npm run typecheck && npm run test:e2e

Üçü de hatasız geçmeli. Test dosyası tek satır değişmeden geçiyorsa taşıma davranışı korumuş demektir.

İpucu

Bu işi bir kodlama ajanına verecekseniz önce Adım 1 ve 2'yi siz yapın. Test ve lint, ajanın işini kendi kontrol edebileceği bir hedef olur.

Kendin Dene

  • features/products içinden @/features/cart/components/cart-provider import edin ve hatayı görün.

  • Bir checkout özelliği ekleyin. Zone listesine kendi satırını yazın; sepet toplamına ihtiyaç duyarsa cartTotal'ı nereye taşımanız gerektiğine karar verin.

  • features/cart/index.ts'i export * ile yazın, sonra useCart'ı oradan import edip import/no-cycle çıktısına bakın.

Kaynaklar

  • 1MakaleMartin Fowler: Definition Of Refactoring

    Refactoring'in tanımı: gözlemlenebilir davranışı değiştirmeden iç yapıyı düzenlemek. 1 Eylül 2004.

  • 2MakaleKent C. Dodds: Testing Implementation Details

    İç ayrıntıya bağlı testlerin refactoring'de yanlış alarm vermesi. 17 Ağustos 2020.

  • 3DokümanESLint: no-restricted-imports

    `patterns` içinde `regex` ile import yolu kısıtlama.

  • 4Repoeslint-plugin-import: no-cycle

    Döngüsel import tespiti ve `maxDepth`.

  • 5Repoeslint-plugin-import: no-restricted-paths

    `target`, `from` ve `except` ile klasörler arası import yasağı.

  • 6Repobulletproof-react: Project Structure

    Feature klasörleri, özellikler arası import yasağı ve tek yönlü akış.

  • 7MakaleKent C. Dodds: Colocation

    Kodu ilgili olduğu yere yakın koyma ilkesi. 17 Haziran 2019.

  • 8Makalejoulev/nextjs-faq: "Module not found" on deployment

    Büyük/küçük harf duyarlılığı yüzünden canlıda kırılan import'lar.

  • 9DokümanNext.js: Installation

    `@/*` alias'ı ve mutlak import'lar.

  • 10DokümanFeature-Sliced Design: Public API

    Özelliğin dışa açık sözleşmesi olarak `index.ts` ve `export *` sorunu.

  • 11MakaleTkDodo: Please Stop Using Barrel Files

    Barrel dosyalarının döngüsel import'a ve yavaşlığa etkisi. 26 Temmuz 2024.

Bu sayfayı düzenle

Son güncelleme tarihi: 22 Ekim 2026

Önceki adım02. Component ve Kod OrganizasyonuSonraki adım04. Monorepo