Skip to content

Kod Konvansiyonları ve Standartlar

İsimlendirme, veri çekme (loader/action, GraphQL vs REST), yetkilendirme, i18n, stil (Tailwind, erişilebilirlik), TypeScript.

Bu sayfa Achidemy projesinde ekip içi tutarlılık için kullanılan kod konvansiyonlarını özetler. Yeni geliştiricinin “nasıl yazmalıyım?” sorusuna yanıt vermeyi hedefler.


  • React bileşenleri: PascalCase (örn. CourseCard.tsx, Navbar.tsx, InstructorSidebar.tsx).
  • Route dosyaları: React Router convention — path segment’leri nokta ile birleştirilir, parametre $ ile yazılır (örn. course.$id.tsx, instructor.course.$slug.manage.curriculum.tsx). Detay için Proje Yapısı — Route dosya isimlendirmesi.
  • API route dosyaları: api.*.ts (örn. api.graphql.ts, api.stripe.webhook.ts).
  • Lib ve yardımcı modüller: kebab-case veya camelCase (örn. db-queries.ts, pricing-engine.ts, graphql-client.ts, video-security.ts).
  • Hook’lar: camelCase, use prefix (örn. useLang.ts, useLearningTracker.ts, useChatNotifications.ts).
  • Event handler fonksiyonları: handle prefix (örn. handleClick, handleSubmit, handleLanguageChange, handleKeyDown).
  • Örnek: onClick={handleClick}, onSubmit={handleSubmit}, onKeyDown={handleKeyDown}.
  • Veri çekme: loader export edilir (async fonksiyon; request, params, context alır).
  • Form / mutation: action export edilir (async fonksiyon; form gönderimi ve API mutation’ları için).
  • Sayfa bileşeni: Default export bir React bileşeni.

Veri çekme: loader vs action, GraphQL vs REST

Section titled “Veri çekme: loader vs action, GraphQL vs REST”
  • Sayfa açıldığında ihtiyaç duyulan veri loader ile çekilir.
  • Loader, context.cloudflare?.env veya getDbUrl(env) ile DB bağlantısı alır; gerekirse initAuth ile session kontrolü yapar.
  • Veri useLoaderData() ile bileşende kullanılır.
  • Çoğu sayfa verisi (kurs listesi, kullanıcı bilgisi, müfredat, admin listeleri vb.) GraphQL üzerinden de çekilebilir; sayfa loader’da graphqlClient ile query atılır veya doğrudan Drizzle sorguları kullanılır.

Route bileşeninde hook sırası ve skeleton

Section titled “Route bileşeninde hook sırası ve skeleton”

Sayfa yüklenirken skeleton göstermek için erken return kullanılıyorsa, React’in hook kurallarına uyulmalıdır. Aksi halde “Rendered fewer hooks than expected” (React #300) hatası oluşur.

Kurallar:

  1. useLoaderData ve tüm hook’lar en üstte: Hiçbir if (loading) return <Skeleton /> veya if (!data) return null hook’lardan önce olmamalı. Önce tüm hook’lar (useNavigation, useLoaderData, useState, useEffect, useTranslation, useLang vb.) aynı sırada çağrılır.
  2. Loader verisi güvenli kullanım: const loaderData = useLoaderData<typeof loader>(); ve alanlar loaderData?.course, loaderData?.curriculum ?? [] gibi optional chaining / nullish ile alınır; hesaplamalar (reduce, map) hook’lardan sonra ve güvenli erişimle yapılır.
  3. Loading ve veri yok return’leri en sonda: Skeleton veya null dönülecekse bu karar tüm hook’lar ve (varsa) hesaplamalar bittikten sonra yapılır: if (navigation.state === "loading") return <PageSkeleton />; ve if (!course) return null; gibi.

Örnek sıra: useNavigationuseLoaderData → tüm useState / useNavigate / useRevalidator / useTranslation / useLang / useEffect → (isteğe bağlı hesaplamalar, güvenli erişimle) → loading/!data return’leri → asıl JSX.

Detay için React Hooks — Route sayfalarında hook sırası ve Hata Yönetimi — React #300 bölümlerine bakın.

  • Form gönderimi, dosya yükleme, durum güncelleme (örn. onay/red) action ile işlenir.
  • Action, request.formData() veya JSON body ile veri alır; yetki kontrolü yapar; DB/API günceller; redirect veya JSON döner.
  • Upload, webhook, download gibi endpoint’ler REST/action kullanır: api.lesson-resource-upload.ts, api.chat-image-upload.ts, api.stripe.webhook.ts, api.certificate.$id.download.ts, api.invoice.$enrollmentId.download.ts — bunlar sayfa değil, API route’u; loader ve/veya action export edilir.
KullanımTercihAçıklama
Sayfa verisi (liste, detay, profil)GraphQL veya loader içinde DrizzleÇoğu sayfa verisi GraphQL query ile; bazı route’lar doğrudan Drizzle kullanır.
Form gönderimi, buton ile tetiklenen işlemaction (form action veya fetch)Mutation GraphQL veya doğrudan API route’a POST.
Dosya yüklemeREST actionapi.lesson-resource-upload, api.chat-image-upload — FormData, multipart.
WebhookREST actionapi.stripe.webhook — Stripe imza doğrulama, raw body.
PDF/ dosya indirmeREST loaderapi.certificate.$id.download, api.invoice.$enrollmentId.download — GET, Response döner.

  • Layout loader’da yapılır. Örneğin:
    • admin.tsx: auth.api.getSession({ headers: request.headers }); session yoksa veya user.role !== "admin" ise redirect("/404").
    • instructor.tsx: Session + eğitmen kaydı / rol kontrolü.
    • account.tsx, my-courses.tsx: Session yoksa giriş sayfasına yönlendirme.
  • Alt sayfalar layout’tan geçtiği için ayrıca session kontrolü yazmaya gerek yok; gerekirse sayfa loader’da ek kontrol yapılabilir.
  • Admin: session?.user?.id ve veritabanından user.role === "admin" kontrolü (örn. admin.tsx loader).
  • Eğitmen: user.role === "instructor" veya eğitmen kaydı tamamlanmış kullanıcı; instructor layout ve ilgili API route’larında kontrol.
  • Öğrenci / giriş gerekli: Sadece session varlığı; rol kontrolü yok (veya sayfa bazında “sadece enrollment sahibi” gibi kısıtlar).
  • Her API route kendi içinde session alır: initAuth(dbUrl, env)auth.api.getSession({ headers: request.headers }).
  • Gerekirse session?.user?.role === "admin" veya "instructor" kontrolü yapılır; yetkisiz ise 401/403 dönülür.

  • Hook: useTranslation() (react-i18next); const { t } = useTranslation();
  • Metin: t("key") veya t("namespace:key") — örn. t("footer.about"), t("nav.language").
  • Çeviri dosyaları: app/locales/*.jsontr.json, en.json, de.json, es.json vb. Her dosyada aynı key yapısı kullanılır.
  • Yeni key ekleme: Tüm dil dosyalarına aynı key’i ekleyin; eksik dilde fallback için varsayılan dil (örn. en) kullanılabilir.
  • Dil ve path: useLang(), getLocalizedPath("/path") — link’lerde dil prefix’li path üretmek için. Dil değiştirme: lib/i18n-utils.tschangeLangInPath, getLangFromPath, shouldIncludeLang.

  • Tailwind: Tüm stil Tailwind sınıfları ile verilir; className="...". Mümkün olduğunca global CSS veya inline style kullanılmaz.
  • Koşullu sınıf: class: (classList mantığı) veya ternary kullanılır. Ekip tercihi: okunabilir olduğu sürece className={cn("base", condition && "active")} veya className={condition ? "a" : "b"}.
  • Erişilebilirlik (a11y):
    • Etkileşimli öğelerde aria-label (örn. ikon butonları: aria-label={t("nav.language")}).
    • Klavye ile erişim için tabIndex={0} (gerektiğinde); onKeyDown ile Enter/Space ile tetikleme.
    • Form alanlarında label veya aria-labelledby kullanılır.

  • Context tipi: Route loader/action’da context genelde { cloudflare?: { env?: unknown } } veya proje tipi ile tanımlanır. env kullanımı: const env = (context?.cloudflare?.env as any) || process.env; (yerelde process.env fallback).
  • Veritabanı: getDb(env.DATABASE_URL) veya getDb(getDbUrl(env))app/db/index.ts. Hyperdrive kullanıldığında production’da env üzerinden Hyperdrive bağlantısı sağlanır.
  • Auth: initAuth(dbUrl, env, ctx?)app/lib/auth.ts; session tipi Better Auth tarafından tanımlanır; Cloudflare Workers ortamında ctx.waitUntil ile auth e-postaları (doğrulama, şifre sıfırlama) arka planda tetiklenir; session?.user?.id, session?.user?.role kullanımı yaygındır.
  • Genel: Mümkün olduğunca any yerine net tipler kullanılır; React Router ve Drizzle şemaları projede tip güvenliği sağlar.

KonuKural
İsimlendirmeBileşenler PascalCase; route dosyaları React Router convention ($param, api.*.ts); event handler’lar handle*; loader/action export.
VeriSayfa verisi → loader (GraphQL veya Drizzle); form/mutation → action; upload/webhook/download → REST action/loader. Route’da skeleton/loading return’ü tüm hook’lardan sonra; useLoaderData erken return’ün önünde.
YetkiLayout loader’da session ve rol kontrolü; session?.user?.role === "admin" / "instructor"; API route’larında kendi session kontrolü.
i18nuseTranslation(), t("key"); çeviriler app/locales/*.json; yeni key tüm dillere eklenir; getLocalizedPath, useLang.
StilTailwind className; koşullu sınıf class: veya ternary; aria-label, tabIndex, onKeyDown, label kullanımı.
TypeScriptcontext.cloudflare?.env; getDb(env.DATABASE_URL); initAuth(dbUrl, env); net tipler tercih edilir.

Detaylı route ve dosya yapısı için Proje Yapısı (Detaylı) ve Route Haritası sayfalarına bakın.