This episode covers how to organize a growing Svelte app: a feature-based project structure, reusable component libraries and composables, separating logic, UI, and data, and scalable Svelte architecture.

As a project grows, folder structure determines development speed. Folders organized by technology — all components in one place, all stores in another — become hard to navigate after hundreds of files. A feature-based approach groups code by domain.
This episode covers a feature-based project structure, reusable component libraries and composables, separating logic, UI, and data, and scalable Svelte architecture.
When you're done, you'll have a mental framework for organizing a project that can grow to tens of thousands of lines without losing direction. Good architecture makes new code feel like filling a prepared slot.
Architecture is expensive to change later, so think about it from the start — but don't overdo it. Start simple and add structure only when needed.
Instead of mixing all components in one folder, group them by business feature:
src/lib/
├── features/
│ ├── auth/
│ │ ├── AuthCard.svelte
│ │ └── server/
│ │ └── session.js
│ ├── cart/
│ │ ├── CartDrawer.svelte
│ │ └── stores.js
│ └── checkout/
├── shared/
│ ├── ui/
│ └── utils/
└── config/The features folder contains everything related to one capability: components, stores, and the accompanying server logic. The shared folder holds cross-feature UI components and utilities that don't depend on any particular domain.
Features shouldn't know each other's internals. Communication between features happens through documented data and events, not deep cross-imports. These boundaries keep changes in one feature from forcing a rewrite of another.
Shared UI components should accept clear props and hold no business logic. Consistent buttons, inputs, and dialogs used everywhere reduce markup duplication and keep the design uniform.
Composables wrap state and behavior so they can be reused without being tied to a single component. Stores are the simplest composable in Svelte:
import { writable } from "svelte/store"
function buatCart() {
const { subscribe, update, set } = writable([])
return {
subscribe,
tambah(item) {
update((items) => [...items, item])
},
kosongkan() {
set([])
},
}
}
export const cart = buatCart()buatCart() returns a store with clear business methods: tambah and kosongkan. Components only need to call cart.tambah(item) without knowing how the store works inside.
Divide files by responsibility. Components only display and dispatch events. Business logic — validation, calculations, decisions — lives in modules that can be tested separately. Data access sits at the backmost layer: load functions on the server, stores for client state.
This separation makes testing easier. Pure logic is tested without a DOM, components are tested with mock data, and UI changes don't touch logic.
Don't duplicate the same state in several places. Choose one owner for data — server or store — and let other components receive it through props or subscriptions. Two out-of-sync data sources are the most productive source of bugs.
Svelte 5 introduced runes for more explicit reactivity. State used by a single component only needs $state; stores remain the best choice for state shared across components and pages.
Move everything dealing with secrets, large data, or logic the client shouldn't see to the server. SvelteKit makes this split easy with the $lib/server module that can only be imported on the server. A healthy architecture maintains that boundary strictly.
Key takeaways:
$state for local state, stores for shared state.$lib/server.Next, in episode 19 you'll learn modern tooling & build automation — TypeScript integration and strict mode, CI/CD pipelines for Svelte apps, and linting, formatting, and pre-commit hooks. A clean architecture must be kept clean by automated tools.