Episode ini membahas arsitektur dan maintainability: feature-based structure dan modular routing, separation of concerns antara UI, data, dan server logic, reusable composables dan utility modules, serta organisasi kode yang skalabel untuk tim.

Setelah beberapa episode membangun fitur, episode 18 membahas cara menata semuanya agar tetap mudah dipelihara saat tim dan kode bertumbuh. Arsitektur yang baik membuat perubahan kecil terasa kecil, bukan membebani satu file dengan tanggung jawab yang tak terkendali.
SvelteKit memberi kebebasan hampir penuh pada struktur folder. Kebebasan itu memerlukan disiplin: konvensi penamaan, batas yang jelas antara lapisan, dan modul yang bisa dipakai ulang. Tanpa itu, setiap fitur baru menjadi semakin mahal.
Setelah episode ini, kalian bisa membangun struktur yang jelas batasannya, mudah dinavigasi, dan tidak membuat pengembang baru tersesat.
Alih-alih menumpuk semua halaman di satu folder route yang panjang, kelompokkan berdasarkan fitur. SvelteKit mendukung route groups dengan folder berawalan tanda kurung, yang ikut membentuk hierarki layout tanpa menambah segmen URL.
src/
routes/
(publik)/
+layout.svelte
about/+page.svelte
(auth)/
+layout.svelte
login/+page.svelte
register/+page.svelte
(app)/
+layout.server.js
dashboard/+page.svelte
lib/
server/
db.js
auth.js
components/
ui/
utils/Route groups memungkinkan layout berbeda untuk grup berbeda: halaman publik tanpa sidebar, halaman aplikasi dengan sidebar dan guard auth. Batas ini membuat tanggung jawab layout tersimpan dekat dengan fitur yang menggunakannya, bukan di satu layout global.
Fungsi data dan logika bisnis tidak seharusnya hidup di dalam komponen. Pisahkan ke layanan di src/lib/server: load functions dan server actions memanggil layanan, komponen hanya menerima data dan menampilkannya.
// src/lib/server/layanan/pengguna.js
import db from "$lib/server/db";
export const daftarPengguna = async ({ halaman, limit }) => {
return db.query(
"SELECT id, nama, email FROM pengguna ORDER BY id DESC LIMIT $2 OFFSET $1",
[halaman, limit]
);
};Load function yang tipis mudah diuji dan dibaca: ia mengambil parameter, memanggil layanan, dan mengembalikan data. Logika rumit dipindah ke layanan yang bisa diuji unit secara mandiri, dan kebijakan otorisasi tetap satu tempat.
import { daftarPengguna } from "$lib/server/layanan/pengguna";
export const load = async ({ locals, url }) => {
const halaman = Number(url.searchParams.get("halaman") ?? 1);
return {
pengguna: await daftarPengguna({ halaman, limit: 20 })
};
};Snippet {@render} memungkinkan mendefinisikan potongan UI sekali dan merendernya berkali-kali, dengan data yang berbeda. Ini mengurangi duplikasi markup tanpa perlu komponen penuh.
{#snippet kartu(item)}
<article class="kartu">
<h3>{item.judul}</h3>
<p>{item.ringkasan}</p>
</article>
{/snippet}
{#each items as item}
{@render kartu(item)}
{/each}Fungsi murni seperti format tanggal, validasi, atau slugify diletakkan di src/lib/utils dan diberi nama yang deskriptif. Fungsi murni tanpa efek samping paling mudah diuji dan dipakai ulang di banyak tempat, termasuk di server.
Tetapkan konvensi penamaan file route, komponen, dan layanan, lalu tuliskan dalam README atau kontribusi. Sertakan juga keputusan arsitektur penting: kapan memakai +server.js, kapan memakai server actions, dan di mana otorisasi ditegakkan.
Jalankan npm run check dan npm run lint secara berkala di CI untuk menegakkan konsistensi. Jenis yang ketat di load functions membuat kontrak antara server dan client eksplisit, sehingga perubahan di satu sisi tidak diam-diam merusak sisi lain.
Inti yang harus dibawa pulang:
src/lib/utils mudah diuji dan dipakai ulang.Di episode 19 selanjutnya kita membahas tooling modern & build automation: integrasi Vite dan preprocessors, setup TypeScript dan strict typing, pipeline CI/CD untuk aplikasi SvelteKit, serta linting, formatting, dan pre-commit hooks.