SvelteKit 3 Geçiş Rehberi: SvelteKit 2’den 3’e Adım Adım Yükseltme

Svelte ekibi 13 Ağustos 2026’da SvelteKit 3 Release Candidate sürümünü duyurdu. Değişikliklerin çoğu küçük ama sayıları fazla. Bazıları da (yapılandırma dosyasının yeri, $lib, $app/stores) sıradan bir projede neredeyse her dosyayı etkiliyor.

Bu yazıyı, yükseltme sırasında karşınıza çıkacakları sırayla yazdım. Örneklerin çoğunu bu blogun kendi kodundan aldım. Site Svelte 5, mdsvex, Paraglide ve Vercel kullanan bir SvelteKit 2 projesi, yani pek de sıra dışı değil. Burada değişmesi gereken şeyler, sizin projenizde de büyük ihtimalle değişecek.

Not: Yazı SvelteKit 3 RC sürümüne göre hazırlandı. Ekip kararlı sürüme kadar yeni bir kırıcı değişiklik beklemediğini söylüyor, yine de başlamadan önce resmî geçiş rehberine bir göz atın.

Özet: SvelteKit 3’te Neler Değişiyor?

Acelesi olanlar için kısa liste:

AlanSvelteKit 2SvelteKit 3
Yapılandırmasvelte.config.jsvite.config.js içinde sveltekit() seçenekleri
Kütüphane takma adı$lib/foo#lib/foo.js (Node subpath imports)
Sayfa durumu$app/stores ($page)$app/state (page), store’lar kaldırıldı
Ortam modülü$app/environment$app/env
Ortam değişkenleri$env/static/*, $env/dynamic/*src/env.ts + $app/env/public / private
Shallow routingpushState / replaceStategoto(url, { shallow: true })
Veriyi yenilemeinvalidateAllrefreshAll
Yollarbase, assets, resolveRouteYalnızca resolve() ve asset()
TypeScript./.svelte-kit/tsconfig.json genişletilir$app/tsconfig genişletilir
HatalarhandleError beklenen hataları atlarhandleError render dahil her hatayı alır
Build aracıVite 5 ile 8 arasıVite 8 zorunlu (Rolldown)
Yanıt yardımcılarıjson(), text()Response.json(), new Response() (eskiler deprecated)
Remote functionsDeneyselHâlâ deneysel

Ortam Hazırlığı

Doğrudan 3’e atlamayın. Ekip önce en güncel 2.x sürümüne geçmenizi öneriyor ve bunun iyi bir sebebi var. 3’teki değişikliklerin önemli bir kısmı 2.x’e isteğe bağlı olarak eklendi (vite.config.js içinde yapılandırma 2.62’de geldi, açık ortam değişkenleri de aynı dönemde). Son 2.x sürümü, değiştirmeniz gereken satırı gösteren deprecation uyarıları da veriyor. Yani yapılacaklar listenizin yarısını size kendisi çıkarıyor.

pnpm add -D @sveltejs/kit@^2
pnpm dev   # terminalde ve tarayıcı konsolunda çıkan deprecation uyarılarını not alın

Sonra ayrı bir dal açın. Codemod onlarca dosyaya dokunacak, diff’i rahat inceleyebilmek istersiniz:

git switch -c chore/sveltekit-3

Adım 1: Otomatik Geçiş Aracını Çalıştırın

SvelteKit, sv CLI ile bir codemod sunuyor:

npx sv@next migrate sveltekit-3 --tasks all --confirm

Araç dönüştürebildiğini dönüştürüyor, emin olamadığı yerler için de bir TODO listesi bırakıyor. Kalanı için SvelteKit 3’ün hata mesajları yol gösteriyor, çoğu neyi değiştirmeniz gerektiğini açıkça söylüyor. Gerisi bildiğimiz döngü: dev sunucusunu aç, hatayı oku, düzelt, tekrar dene.

Yine de codemod bitti diye rehberin kalanını atlamayın. Hata yönetimi, çerezler, goto‘nun artık reddettiği URL’ler ya da sürüm kontrolü gibi konularda kod derleniyor ama farklı davranıyor. Bunları bir araç sizin yerinize bilemez.

Adım 2: Araç Zincirini Yükseltin

Minimum sürümlerin hepsi yukarı çekildi:

PaketMinimum sürüm
Node.js22.17
TypeScript6
svelte5.57.1
vite8.0.12
@sveltejs/vite-plugin-svelte7
pnpm add -D @sveltejs/kit@next svelte@latest vite@^8 @sveltejs/vite-plugin-svelte@^7 typescript@^6

Vite 8.0.12, kararlı Rolldown v1 ile gelen ilk sürüm. Rolldown, Rust ile yazılmış ve esbuild ile Rollup’ın yaptığı işi tek başına üstlenen bir paketleyici. Kodda hiçbir şey değiştirmeden build’leriniz hızlanıyor.

Bu adımda iki şeye bakın:

  1. CI ve hosting tarafındaki Node sürümü. 22.17 altı artık yok. package.json içindeki engines alanını, .nvmrc dosyasını ve hosting panelindeki ayarı güncellemeyi unutmayın. CI’da eski Node ile build alınca çıkan hata her zaman ilk bakışta anlaşılmıyor.
  2. Vite eklentileri. Her eklentinin Vite 8 desteği olmalı. Bu blogda kontrol edilecekler Tailwind (@tailwindcss/vite), @sveltejs/enhanced-img, Paraglide, Traceway eklentisi ve rollup-plugin-visualizer. Rollup eklentilerinin çoğu Rolldown altında sorunsuz çalışıyor, ama bir hata görünce SvelteKit’i suçlamadan önce eklentileri tek tek kontrol edin.

Adım 3: svelte.config.js Dosyasını vite.config.js İçine Taşıyın

En göze batan değişiklik bu: svelte.config.js artık okunmuyor. Tüm ayarlar doğrudan sveltekit() eklentisine veriliyor. kit.* altındakiler bir üst seviyeye çıkıyor ve compilerOptions, preprocess gibi Svelte ayarlarıyla yan yana duruyor.

Sebebi teknik ama makul. Vite eklentisi yapılandırmaya en başta ihtiyaç duyuyor. İkinci bir dosyayı asenkron okumak, örneğin farklı bir dizinden başlatılan Vitest’te sorun çıkarıyordu. Ayarlar tek yerde olunca bu dert kalmıyor. Açıkçası iki dosya arasında gidip gelmemek de ayrı bir rahatlık.

Önce (SvelteKit 2):

// svelte.config.js
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { mdsvex } from "mdsvex";

import mdsvexConfig from "./mdsvex.config.js";

export default {
  extensions: [".svelte", ".md"],
  preprocess: [vitePreprocess(), mdsvex(mdsvexConfig)],
  kit: {
    adapter: adapter(),
    alias: {
      $store: "src/store/index.js",
      $posts: "src/posts",
    },
    csrf: { checkOrigin: false },
  },
};

Sonra (SvelteKit 3):

// vite.config.js
import adapter from "@sveltejs/adapter-auto";
import { sveltekit } from "@sveltejs/kit/vite";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { mdsvex } from "mdsvex";
import { defineConfig } from "vite";

import mdsvexConfig from "./mdsvex.config.js";

export default defineConfig({
  plugins: [
    sveltekit({
      adapter: adapter(),
      extensions: [".svelte", ".md"],
      preprocess: [vitePreprocess(), mdsvex(mdsvexConfig)],
      csrf: { trustedOrigins: ["https://trusted-site.com"] },
    }),
  ],
});

SvelteKit’in tanımadığı seçenekler vite-plugin-svelte‘e aktarılıyor, dolayısıyla inspector gibi ayarları da buraya yazabilirsiniz. experimental alanı ikisi arasında ortak: SvelteKit kendi bayraklarını alıyor, gerisini iletiyor.

Kaldırılan, eklenen ve değişen seçenekler

SeçenekNe yapmalı
vitePluginKaldırıldı. vite-plugin-svelte seçeneklerini doğrudan sveltekit()‘e verin
files.libKaldırıldı. Yerine #lib subpath import kullanın
preloadStrategyKaldırıldı. Artık her zaman modulepreload kullanılıyor
prerender.originKaldırıldı. paths.origin kullanın
csrf.checkOriginKaldırıldı. CSRF hep açık, izinli alan adları için csrf.trustedOrigins
experimental.tracingArtık üst düzey tracing seçeneği
experimental.instrumentationGerek yok. src/instrumentation.server.js varsa otomatik algılanır
experimental.handleRenderingErrorsGerek yok. Render hataları her zaman yakalanıyor
paths.origin (yeni)CSRF kontrolleri için genel origin. adapter-node’da ORIGIN değişkeninin yerini alır
output.linkHeaderPreload (yeni)Preload için Link header’larına geri dönmek için. Varsayılan <link> öğeleri
version.pollInterval (değişti)Varsayılan artık bir saat. Önceden hiç yoklama yapılmıyordu

Tablodaki son satır kolayca gözden kaçıyor. SvelteKit 3 yeni bir deploy olup olmadığını saatte bir kontrol ediyor. Bunun dışında sekmeye geri dönüldüğünde, sayfa tekrar görünür olduğunda ve sunucuya giden her veri isteğinde ya da remote function çağrısında da bakıyor. Yeni sürüm bulursa updated.current değerini true yapıyor. Sitenizde “Yeni sürüm var, sayfayı yenileyin” gibi bir uyarı varsa artık çok daha sık çıkacak. Çoğu zaman istediğiniz de zaten bu.

Adım 4: $lib Yerine #lib Kullanın

$lib takma adını SvelteKit artık kendisi tanımlamıyor. Onun yerine Node’un subpath imports özelliği kullanılıyor. Vite ve TypeScript bunu zaten tanıyor, takma adı ise siz package.json içinde tanımlıyorsunuz:

{
  "imports": {
    "#lib": "./src/lib/index.js",
    "#lib/*": "./src/lib/*"
  }
}

Importlar da şöyle değişiyor:

// önce
import { cn } from "$lib/utils";
import { Seo } from "$lib/components";

// sonra
import { cn } from "#lib/utils/index.js";
import { Seo } from "#lib/components/index.js";

Zamanınızın çoğunu bu adım alacak, çünkü dosya uzantısı artık zorunlu. Node subpath importlarda tahmin yürütmüyor, #lib/utils yazınca #lib/utils/index.js dosyasını kendisi bulmuyor. Uzantısız barrel importu ise SvelteKit projelerinde neredeyse standart. Bu blogda saydım: Paraglide’ın ürettikleri hariç 64 $lib importunun 63’ünde uzantı yok. Sadece $lib/utils 38 kez, $lib/components 15 kez geçiyor. Diff’in en kalabalık kısmı muhtemelen burası olacak.

TypeScript kullanıyorsanız bir ayrıntı daha var: kaynak dosya .ts olsa bile import yoluna .js yazıyorsunuz. Kulağa ters geliyor ama TypeScript bunu doğru dosyaya eşliyor. NodeNext ile çalıştıysanız bu kurala zaten aşinasınızdır.

Özel takma adlar

Resmî rehber sadece $lib‘i kaldırıyor, kendi tanımladığınız takma adlara dokunmuyor. Ama madem bu dosyaları elden geçiriyorsunuz, onları da subpath importlara taşımayı düşünebilirsiniz. Böylece Node, Vite ve TypeScript ayrı ayarlara gerek kalmadan aynı yolu çözüyor:

{
  "imports": {
    "#lib": "./src/lib/index.js",
    "#lib/*": "./src/lib/*",
    "#store": "./src/store/index.js",
    "#posts/*": "./src/posts/*"
  }
}

Tek şart, adların # ile başlaması. $store, $posts gibi takma adları taşıyacaksanız isimleri de değişecek.

Adım 5: tsconfig.json Dosyasını Güncelleyin

tsconfig.json (ya da jsconfig.json) artık $app/tsconfig dosyasını genişletiyor. Bu dosya node_modules/$app altına otomatik yazılıyor ve içinde isolatedModules, verbatimModuleSyntax gibi önerilen ayarlar hazır geliyor. Kendi compilerOptions bloğunuzun büyük kısmını muhtemelen silebilirsiniz.

{
  "extends": "$app/tsconfig",
  "include": ["src", "test", "*"],
  "exclude": ["src/service-worker"]
}

include ve exclude artık sizin için doldurulmuyor, bunları elle yazın. Service worker kullanıyorsanız onun için ayrı bir src/service-worker/tsconfig.json açıp $app/tsconfig/service-worker dosyasını genişletmeniz gerekiyor.

Adım 6: $app/* Importlarını Güncelleyin

$app/stores kaldırıldı

Svelte 4’ten kalan store’lar tamamen gitti. Yerine Svelte 5’in rune’larıyla çalışan $app/state var. Import satırını değiştirip $ önekini silmeniz yeterli:

<script>
// önce
// import { page } from "$app/stores";
import { page } from "$app/state";
</script>

<!-- önce: {$page.url.pathname} --><p>Geçerli yol: {page.url.pathname}</p>

Bu blogda 7 route dosyası hâlâ $app/stores kullanıyor, çoğu da sadece $page.url.pathname değerini SEO bileşenine geçmek için. Her biri iki satırlık iş, ama biri bile kalsa SvelteKit 3 build almıyor.

Bir de page.url artık salt okunur. Tipi ReadonlyURL, query parametreleri de ReadonlyURLSearchParams. Filtre sayfalarında page.url.searchParams.set(...) yazmak çok yaygındı, artık önce kopyalamanız gerekiyor:

const url = new URL(page.url.href);
url.searchParams.set("q", "svelte");
goto(url);

$app/environment artık $app/env

Burada değişen sadece isim. browser, dev, building ve version artık $app/env modülünden geliyor. Bir artısı da var: bu modülü service worker içinde de kullanabiliyorsunuz.

// önce
import { browser, dev } from "$app/environment";

// sonra
import { browser, dev } from "$app/env";

$app/navigation: shallow routing, refreshAll ve goto

Shallow routing için pushState ve replaceState yerine artık goto kullanılıyor, shallow: true ile:

import { goto } from "$app/navigation";

// önce: pushState("/photos/42", { selected: 42 });
goto("/photos/42", { shallow: true, state: { selected: 42 } });

// önce: replaceState("/photos/42", { selected: 42 });
goto("/photos/42", { shallow: true, replace: true, state: { selected: 42 } });

Dikkat: shallow geçişler de artık beforeNavigate, onNavigate ve afterNavigate fonksiyonlarını çalıştırıyor. onNavigate içinde View Transitions başlatıyor ya da analitik olayı gönderiyorsanız, navigasyon nesnesindeki shallow alanına bakıp bu durumları atlayın. Yoksa bir fotoğraf modalı açıldığında sayfa geçiş animasyonu oynar ya da analitiğe sahte bir sayfa görüntüleme düşer.

Güzel bir yenilik de persistState: true. Sayfa yenilendiğinde page.state geri yükleniyor, yani URL’e bağlı açılan bir modal F5’ten sonra kaybolmuyor.

Diğer goto değişiklikleri:

SvelteKit 2SvelteKit 3
invalidateAll()refreshAll()
goto(url, { invalidateAll: true })goto(url, { refreshAll: true })
goto(url, { keepFocus: true, noScroll: true })goto(url, { reset: false })
goto(url, { replaceState: true })goto(url, { replace: true })

İki davranış değişikliği özellikle önemli:

  • goto artık hiçbir route’a uymayan URL’lerde hata fırlatıyor. Eskiden bunu sadece harici adreslerde yapıyordu. Uygulama dışına çıkmak için window.location.href = url kullanın.
  • delta sadece popstate geçişlerinde dolu geliyor. Link tıklamalarında ve goto çağrılarında değeri undefined.

refreshAll ile invalidateAll arasında küçük ama işe yarar bir fark var: refreshAll, page.state değerini sıfırlamıyor. Ayrıca bir geçiş devam ederken invalidate ya da refreshAll çağırırsanız o geçiş artık yarıda kesilmiyor.

preloadData(...) sonucunu kullanan bir kodunuz varsa, yeni { type: "error", status, error } durumunu da ele alın. Eskiden hata veren sayfalar için bile 200 durumuyla loaded dönüyordu.

$app/paths: yalnızca resolve ve asset kaldı

Deprecated olan base, assets ve resolveRoute tamamen kaldırıldı. Asıl dikkat isteyen kısım şu: yol adları (pathname) artık / ile başlamıyor. Baştaki eğik çizgi sadece route ID’lerinde kalıyor:

import { asset, resolve } from "$app/paths";

// yol adı: baştaki eğik çizgi yok
const about = resolve("about");

// route ID + parametreler: baştaki eğik çizgi var
const post = resolve("/blog/[slug]", { slug: "hello-world" });

// statik dosya: baştaki eğik çizgi yok
const logo = asset("logo.png");

Tip adları da değişti: Pathname yerine Path, Asset yerine AssetPath. Projede resolve("/ ve asset("/ diye arama yapıp her sonuca tek tek bakmanızı öneririm. Argümanın yol adı mı yoksa route ID mi olduğunu codemod her zaman ayırt edemiyor.

Adım 7: Açık Ortam Değişkenlerine Geçin

$env/static/* ve $env/dynamic/* modülleri SvelteKit 3’te deprecated oldu ve SvelteKit 4’te tamamen kaldırılacak. Şimdilik çalışmaya devam ediyorlar, yani bu adımı ertelemek mümkün. Ama zaten bu dosyaların içindeyken yapmak daha mantıklı.

Yeni yaklaşımda kullandığınız her değişkeni src/env.ts dosyasında tek tek tanımlıyorsunuz. Hangisinin tarayıcıya gidebileceğini belirtiyor, isterseniz herhangi bir Standard Schema kütüphanesiyle doğruluyorsunuz.

// src/env.ts
import { defineEnvVars } from "@sveltejs/kit/env";
import * as v from "valibot";

import { building } from "$app/env";

export const variables = defineEnvVars({
  PUBLIC_SITE_URL: {
    public: true,
    schema: v.pipe(v.string(), v.url()),
  },
  PUBLIC_MEASUREMENT_ID: {
    public: true,
    schema: v.pipe(v.string(), v.regex(/^G-[A-Z0-9]+$/)),
  },
  SECRET_TURNSTILE_KEY: {
    // build sırasında isteğe bağlı, sunucu başlarken zorunlu
    schema: building ? v.optional(v.string()) : v.string(),
  },
});

Kullanırken de yeni modüllerden import ediyorsunuz:

// önce
import { PUBLIC_MEASUREMENT_ID } from "$env/static/public";
import { SECRET_TURNSTILE_KEY } from "$env/static/private";

// sonra
import { SECRET_TURNSTILE_KEY } from "$app/env/private";
import { PUBLIC_MEASUREMENT_ID } from "$app/env/public";

İlk bakışta fazladan iş gibi görünüyor ama karşılığını veriyor. Eksik ya da yanlış bir değer artık çalışma sırasında sessizce undefined olarak dönmüyor, build ya da sunucu açılırken hata veriyor. Tipler şemadan geldiği için ayrıca tip yazmıyorsunuz. public: true demediğiniz hiçbir değişken tarayıcıya gitmiyor ve $app/env/private istemci kodunda import bile edilemiyor. Public değişkenleri app.html içinde %sveltekit.env.PUBLIC_MEASUREMENT_ID% şeklinde de kullanabiliyorsunuz, analitik kodu eklerken epey işe yarıyor.

Bu blogda $env/static/* kullanan 13 dosya var: SEO, analitik, footer, RSS, site haritası ve iletişim formu API’si. Hepsini taşımak sıkıcı ama zor değil. Karşılığında Turnstile anahtarı girilmeden yapılmış bir production deploy’u, iletişim formu bozulduktan sonra değil, daha build aşamasında fark ediyorsunuz.

Adım 8: Hata Yönetimini Gözden Geçirin

SvelteKit 2, Svelte 4 desteğini sürdürmek zorundaydı. Svelte 4’te de error boundary diye bir şey yoktu. Bu yüzden +error.svelte sadece load sırasında oluşan hataları gösterebiliyordu. SvelteKit 3 doğrudan Svelte 5 istiyor ve arka planda <svelte:boundary> kullanıyor.

Değişenler:

  1. Render hataları da yakalanıyor. Bir bileşen render edilirken hata atarsa bu hata önce handleError‘dan geçiyor, sonra en yakın +error.svelte bileşeninde gösteriliyor.
  2. handleError her hatayı alıyor. error(404, ...) ile bilerek oluşturduğunuz hatalar da buna dahil. Önceden bunlar handleError‘a hiç uğramıyordu.
  3. handleError durum kodunu değiştirebiliyor. Dönüş değerine bir status eklemeniz yeterli.
  4. App.Error artık her zaman bir status içeriyor.
  5. error() imzası değişti. İkinci argüman sadece string olabiliyor, ek alanlar üçüncü argümana gidiyor.
  6. handleValidationError kaldırıldı. Doğrulama hataları kind: "validation" ile handleError‘a geliyor.
  7. Stack trace’lerde varsayılan olarak sourcemap kullanılıyor.

Gerçek hayatta başınızı en çok 2. madde ağrıtacak. Bu blog hataları Traceway’e gönderiyor ve SvelteKit 2’deki hooks.client.js dosyası şu an şöyle:

// src/hooks.client.js (SvelteKit 2)
export function handleError({ error, event }) {
  captureExceptionWithAttributes(error, {
    route: event.route?.id ?? event.url.pathname,
  });
  return { message: "Something went wrong" };
}

Bu dosya SvelteKit 3’e olduğu gibi taşınırsa, olmayan bir sayfaya giren her ziyaretçi Traceway’de yeni bir hata kaydı oluşturur. Bir botun siteyi taradığı bir gecenin sabahında panoyu açtığınızı düşünün. Beklenen hataları ayıklamak şart:

// src/hooks.client.js (SvelteKit 3)
import { isHttpError } from "@sveltejs/kit";

/** @type {import("@sveltejs/kit/hooks").HandleClientError} */
export function handleError({ error, event, status }) {
  // 404 gibi beklenen hatalar da artık buraya geliyor
  if (!isHttpError(error) || status >= 500) {
    captureExceptionWithAttributes(error, {
      route: event.route?.id ?? event.url.pathname,
    });
  }

  return { message: "Something went wrong", status };
}

Hook tiplerinin yeri de değişti: Handle, HandleClientError ve diğerleri artık @sveltejs/kit/hooks içinde. hooks.client.js içindeki handleError asenkron ise, render sırasında beklenebilmesi için compilerOptions.experimental.async ayarını açmanız gerekiyor.

Adım 9: Sunucu, Güvenlik ve Yanıt Değişiklikleri

Bu bölümdeki maddeler tek tek küçük, ama her biri production’da bir şeyi sessizce bozabilir:

  • json() ve text() deprecated oldu. Yerine platformun kendi API’lerini kullanın: Response.json(data, init) ve new Response(text). Bu blogda dört API route’u json() kullanıyor, değişiklik her birinde tek satır.
  • 204 yanıtları artık boş dönüyor. +server.js içinden gövdesiz bir 2xx döndürdüğünüzde SvelteKit araya kendi JSON’unu eklemiyor. HTTP standardının istediği de bu zaten.
  • Çerezler cookie v2 ile işleniyor. Çerez adlarında sadece ASCII karakter kullanılabiliyor. Çerez adında ç, ş, ğ gibi bir harf kullandıysanız artık reddediliyor. path vermeden ayarlanan çerezler de istek yolu yerine varsayılan olarak / yolunu alıyor.
  • Harici adreslere yönlendirme için izin gerekiyor: redirect(307, "https://example.com", { external: true }) yazın ya da izin verilen origin’leri dizi olarak verin.
  • Content-Type header’ı olmayan çapraz origin form gönderimleri CSRF sayılıp reddediliyor.
  • Sunucuya özel modül tanımı genişledi. Adında server geçen her dosya (server.ts, db.server.ts) ve sadece src/lib/server değil, adı server olan her klasör artık sunucuya özel. src/lib/components/server gibi bir klasörünüz varsa bir anda istemcide import edilemez hale gelebilir.
  • Parametre eşleyicileri (param matchers) src/params klasöründen çıkıp tek bir src/params.ts dosyasında toplanıyor. Burada @sveltejs/kit/params içinden gelen defineParams kullanılıyor ve bir eşleyici doğrudan Standard Schema da olabiliyor.
  • data-sveltekit-* niteliklerinde "off" yerine artık false yazılıyor.
  • Bulunduğunuz sayfaya giden bir linke tıklamak önceden hiçbir şey yapmıyordu, şimdi refreshAll() tetikliyor.
  • +page.js içindeki config artık +page.server.js içindekine göre öncelikli.
  • Service worker’lar type: "module" ile kaydediliyor. Eski $service-worker modülü kaldırıldı, yerine $app/env, $app/manifest, $app/paths ve tipleriyle birlikte gelen $app/service-worker var.

Adım 10: Adapter’ınızı Kontrol Edin

Resmî adapter’ların hepsi SvelteKit 3 istiyor. Her birinde ayrıca şunlar değişti:

AdapterDeğişiklik
adapter-verceledge çalışma ortamı artık desteklenmiyor. Edge route’larını Node.js çalışma ortamına taşıyın
adapter-nodeRolldown ile paketleniyor. ORIGIN değişkeni kaldırıldı (paths.origin kullanın). Statik dosyalar build anında sabitleniyor
adapter-cloudflareplatform.env ve platform.context yok. env ve waitUntil değerlerini cloudflare:workers modülünden alın
adapter-netlifyKararlı Netlify Frameworks API’sini kullanıyor. Netlify CLI 17.31+ gerektiriyor

Vercel kullanıyorsanız edge desteğinin kalkmasına özellikle dikkat edin. Bir route config = { runtime: "edge" } dışa aktarıyorsa, deploy etmeden önce onu Node.js runtime’ına almanız gerekiyor. Bu blogda edge kullanan bir route yok, ama bazı projelerde bu tek satır deploy’u tamamen durdurabilir.

adapter-node tarafında da küçük bir sürpriz var. Statik dosyaların listesi build sırasında çıkarılıyor, sonradan çıktı klasörüne kopyaladığınız dosyalar sunulmuyor. Çalışma zamanında dosya üretiyorsanız onları bir route üzerinden servis edin.

SvelteKit 3’te Remote Functions

2.x döneminde SvelteKit Remote Functions ile tanıştıysanız şunu bilin: SvelteKit 3’te de hâlâ deneysel. Bayraklar diğer ayarlarla birlikte vite.config.js içine taşınıyor:

sveltekit({
  compilerOptions: { experimental: { async: true } },
  experimental: { remoteFunctions: true },
});

Mevcut remote kodunuzda değişenler:

  • Adında remote geçen her dosya (posts.remote.ts, remote.ts) remote modül sayılıyor. Bayrağı açmadan bu dosyalardan biri projede durursa hata alıyorsunuz.
  • query içinde event.url, event.params ya da event.route okumaya çalışmak artık hata fırlatıyor. İhtiyacınız olan değeri fonksiyona argüman olarak geçin.
  • Sorgu ve formlardaki error alanının tipi any değil, App.Error | undefined.
  • Form alanlarında niteliği field’dan almak zorunlu, örneğin myForm.fields.message.as("text"). Elle yazılmış name="message" artık kabul edilmiyor.
  • RemoteQuery, RemoteForm gibi tipler $app/server modülüne taşındı.

Performansa Etkisi

Bu blog performans üzerine olduğu için bu kısmı atlayamam. SvelteKit 3 kullanıcı tarafında ve build tarafında neyi değiştiriyor?

Build süresi. İlk fark edeceğiniz şey muhtemelen bu olacak. Vite 8 ile esbuild ve Rollup’ın yerini tek başına Rolldown alıyor.

Preload yöntemi. SvelteKit 3 modülleri Link header’ları yerine HTML içine eklediği <link rel="modulepreload"> öğeleriyle önceden yüklüyor. Bazı hosting’lerde header’lar fazla büyüyüp sorun çıkarıyordu, bu değişiklik onu da çözüyor. modulepreload bütün modern tarayıcılarda desteklendiği için preloadStrategy seçeneğine de gerek kalmadı.

Daha az yeniden render. $page store’unda herhangi bir alan değişince ona abone olan bütün bileşenler yeniden çalışıyordu. $app/state ile bir bileşen sadece okuduğu alan değişirse güncelleniyor. Sayfalar arasında çok gezinilen sitelerde bu, ekstra bir şey yapmadan INP değerine küçük de olsa iyi geliyor.

Okunabilir hata kayıtları. Sourcemap’ler stack trace’lere uygulandığı için production’da bir sorun çıktığında hatanın kaynağını bulmak kolaylaşıyor.

Sürüm kontrolü. Saatte bir ve sekmeye dönüldüğünde küçük bir istek gidiyor. Kullanıcı bunu hissetmez ama loglarda görürsünüz, şaşırmayın.

Sayfaları prerender edip Speculation Rules kullanıyorsanız SvelteKit 3’te bununla çakışan bir şey yok. Sadece data-sveltekit-preload-data="off" yazdığınız yerleri "false" olarak güncellemeyi unutmayın.

SvelteKit 3 Geçiş Kontrol Listesi

Pull request açıklamasına yapıştırıp tek tek işaretleyebilirsiniz:

  • En güncel SvelteKit 2.x sürümüne geçildi ve deprecation uyarıları giderildi
  • npx sv@next migrate sveltekit-3 --tasks all --confirm çalıştırıldı ve TODO listesi incelendi
  • Node 22.17+, TypeScript 6, Svelte 5.57.1+, Vite 8.0.12+, vite-plugin-svelte 7
  • Tüm Vite eklentileri Vite 8 ve Rolldown ile doğrulandı
  • svelte.config.js, vite.config.js içine taşındı ve silindi
  • csrf.checkOrigin, prerender.origin, preloadStrategy, vitePlugin kaldırıldı veya değiştirildi
  • #lib, package.json içinde tanımlandı ve tüm $lib importları dosya uzantısıyla yeniden yazıldı
  • tsconfig.json / jsconfig.json, açık include / exclude ile $app/tsconfig genişletiyor
  • $app/stores yerine $app/state kullanılıyor, hiçbir kod page.url değerini değiştirmiyor
  • $app/environment yerine $app/env kullanılıyor
  • pushState / replaceState / invalidateAll ve eski goto seçenekleri taşındı
  • resolve() ve asset() çağrıları baştaki eğik çizgi için kontrol edildi, base / assets kaldırıldı
  • src/env.ts oluşturuldu, $env/* importları $app/env/public / private modüllerine taşındı
  • handleError raporlamadan önce beklenen hataları filtreliyor, error() çağrıları string mesaj kullanıyor
  • json() / text() yerine Response.json() / new Response() kullanılıyor
  • Çerezler, harici yönlendirmeler ve parametre eşleyicileri gözden geçirildi
  • Adapter notları uygulandı (Vercel edge, ORIGIN, Cloudflare bağlamaları)
  • Production build başarılı, preview deploy’da ana sayfalar elle kontrol edildi

Sonuç

SvelteKit 3’ü yeni bir framework gibi görmeyin. Bu sürüm daha çok bir temizlik. SvelteKit, Svelte 4 ve eski araçlar yüzünden yıllardır taşıdığı yükü bırakıyor. Store’ların yerini rune’lar alıyor, $lib yerine Node’un kendi import sistemi geliyor, svelte.config.js Vite ayarlarının içine giriyor ve render sırasında atılan hatalar da sonunda yakalanıyor.

İşin büyük kısmı mekanik, onu da büyük ölçüde codemod hallediyor. Asıl düşünmeniz gereken yerler kodun davranışının değiştiği noktalar. Hangi hataları raporlayacaksınız? Hangi goto çağrıları artık hata fırlatabilir? Çerezleriniz ve yönlendirmeleriniz eskisi gibi çalışıyor mu? Adapter’ınızda kaldırılan bir özellik kullanıyor musunuz? Benim tavsiyem, kontrol listesini ayrı bir dalda sırayla uygulamanız ve production’a çıkmadan önce mutlaka bir preview deploy’da denemeniz.

Geçişte takıldınız mı? Her projenin kendine has sorunları oluyor: Rolldown ile anlaşamayan bir eklenti, bir türlü çözülmeyen bir import yolu ya da sadece production’da patlayan bir deploy. Böyle bir sorunla karşılaşırsanız benimle iletişime geçin. Ne gördüğünüzü anlatın, birlikte çözelim. SvelteKit 3 geçişinin tamamını ya da performans iyileştirmelerini de üstlenebilirim.

Sıkça Sorulan Sorular

SvelteKit 3 kararlı mı?

SvelteKit 3, 13 Ağustos 2026’da Release Candidate aşamasına geçti. Svelte ekibi kararlı sürümden önce yeni bir kırıcı değişiklik planlamadığını söylüyor. Bu yüzden şimdi geçiş yapmak çoğu uygulama için düşük risklidir, ancak production’a çıkmadan önce kapsamlı test yapmalısınız.

SvelteKit 2’den SvelteKit 3’e nasıl geçilir?

Önce en güncel SvelteKit 2.x sürümüne geçip tüm kullanımdan kaldırma uyarılarını giderin. Ardından npx sv@next migrate sveltekit-3 komutunu çalıştırın. Bu araç kodunuzun büyük kısmını otomatik günceller ve kalanlar için bir TODO listesi üretir. Son olarak Node’u 22.17+, TypeScript’i 6, Svelte’i 5.57.1+, Vite’ı 8 ve vite-plugin-svelte‘i 7 sürümüne yükseltin.

SvelteKit 3’te svelte.config.js hâlâ destekleniyor mu?

Hayır. Tüm yapılandırma vite.config.js veya vite.config.ts içindeki sveltekit() eklenti çağrısına taşınır. Eskiden kit.* altında duran seçenekler eklentinin üst düzey seçenekleri olur, compilerOptions ve preprocess gibi Svelte seçenekleri de onların yanında yer alır.

$lib neden #lib ile değiştirildi?

SvelteKit 3, Vite ve TypeScript’in zaten anladığı Node’un yerel subpath imports özelliğini kullanır. #lib‘i package.json dosyasının imports alanında siz tanımlarsınız ve SvelteKit artık araçlar arasında özel bir takma adı koordine etmek zorunda kalmaz. Dikkat edilmesi gereken nokta, importlarda dosya uzantısının zorunlu olmasıdır: $lib/utils yerine #lib/utils/index.js yazmalısınız.

SvelteKit 3’te $app/stores yerine ne kullanılır?

$app/stores kaldırıldı. page, navigating ve updated değerlerini $app/state modülünden içe aktarın ve $ önekini kaldırın, yani $page.url artık page.url olur. page.url artık salt okunurdur, değiştirmeniz gerekiyorsa önce new URL(page.url.href) ile kopyalayın.

$env/static/public ve $env/static/private kaldırıldı mı?

SvelteKit 3’te kullanımdan kaldırıldılar (deprecated) ve SvelteKit 4’te tamamen silinecekler. Yerine src/env.ts dosyasında defineEnvVars ile tanımlanan ve $app/env/public ya da $app/env/private modüllerinden içe aktarılan açık ortam değişkenleri gelir. İsteğe bağlı olarak Standard Schema ile doğrulama da yapılabilir.

Remote functions SvelteKit 3’te kararlı hale geliyor mu?

Henüz değil. Remote functions SvelteKit 3’te de compilerOptions.experimental.async ile birlikte experimental.remoteFunctions bayrağının arkasında duruyor. Bazı davranışları ise değişti, örneğin query fonksiyonları artık event.url veya event.params değerlerini okuyamıyor.

entr