Nếu bạn đã dành vài năm qua để nhảy qua lại giữa các meta-framework, SvelteKit 2 sẽ mang lại một cảm giác vừa nhẹ nhõm vừa nghi hoặc. Nhẹ nhõm, vì nó thực sự loại bỏ sự phức tạp thay vì làm nó tăng thêm. Nghi hoặc, vì bạn cứ mãi chờ đợi một vấn đề gì đó phát sinh. Nhưng điều đó không bao giờ thực sự xảy ra. Kết hợp với Svelte 5, stack này là một trong những cách hiệu quả nhất để triển khai một ứng dụng full-stack vào năm 2026, và các con số đã chứng minh cho trải nghiệm nhà phát triển này. Các bundle được tạo ra nhỏ hơn khoảng 35% so với những gì Svelte 4 đã làm. Định tuyến (routing), các hàm máy chủ (server functions) và các mẫu xác thực (authentication patterns) đều là những thành phần cốt lõi, chứ không phải là những plugin được chắp vá lại với nhau.
Runes giúp tính phản ứng trở nên tường minh
Sự thay đổi tư duy lớn nhất đến từ các runes của Svelte 5. Các phiên bản Svelte trước đây sử dụng nhãn $: và rất nhiều "phép thuật" của trình biên dịch để theo dõi các phụ thuộc (dependencies). Nó hoạt động tốt, nhưng khi có lỗi xảy ra, bạn sẽ phải debug những "sợi dây vô hình". Runes thay thế sự kỳ diệu đó bằng các hàm tường minh. Bạn cho trình biên dịch biết chính xác cần theo dõi cái gì, và nó sẽ lắng nghe.
Dưới đây là những gì bạn cần biết.
- $state xử lý các biến phản ứng. Bao bọc bất kỳ giá trị nào trong
$state()và trình biên dịch sẽ biết cách theo dõi nó. - $derived tính toán các giá trị từ các state khác. Bạn cần một danh sách đã lọc hoặc một tổng số đã được định dạng? Hãy dùng
$derived. Điểm khác biệt chính so với$effectlà$deriveddành cho các giá trị, không phải cho các hành động. - $effect thực hiện các tác dụng phụ (side effects). Hãy nghĩ đến việc cập nhật tiêu đề tài liệu (document title), đo lường DOM thủ công, hoặc các bộ hẹn giờ cần được dọn dẹp (cleanup). Nó chạy sau khi DOM đã được commit, tương tự như một lifecycle hook nhưng gắn liền với các phụ thuộc phản ứng cụ thể.
- $props thay thế mô hình
export letcũ để nhận dữ liệu trong các component. Nó rõ ràng hơn và tương tác tốt hơn với TypeScript.
Mô hình này mang lại hiệu quả trong thực tế. Vì trình biên dịch chỉ theo dõi những gì bạn đánh dấu, mã chết (dead code) sẽ thực sự là mã chết. Bạn sẽ không còn phải thắc mắc tại sao một biến lại kích hoạt một bản cập nhật, và bạn bắt đầu tin tưởng vào các chỉ dẫn tường minh của chính mình.
Định tuyến theo thư mục, không phải theo cấu hình
SvelteKit sử dụng hệ thống tệp của bạn để định tuyến. Không có tệp router riêng biệt nào cần phải duy trì. Chỉ cần thả một tệp +page.svelte vào một thư mục, và thư mục đó sẽ trở thành một route đang hoạt động.
Giao diện dùng chung (Shared UI) bao bọc các route đó thông qua +layout.svelte. Đặt một tệp ở thư mục gốc, và mọi route con sẽ được kế thừa nó. Đặt một tệp sâu hơn trong cây thư mục, và chỉ phần đó mới nhận được wrapper.
Logic phía máy chủ nằm trong +page.server.ts. Phần này chạy trước khi trang của bạn được render, vì vậy đây là nơi bạn truy vấn cơ sở dữ liệu, xác thực cookie hoặc từ chối người dùng chưa được xác thực. Các kiểu dữ liệu (types) sẽ tự động chảy từ hàm load của bạn vào component trang, nghĩa là dữ liệu của bạn đã được định kiểu mà không cần viết các interface thủ công.
Các endpoint API thuần túy nằm trong các tệp +server.ts. Chúng xuất ra các trình xử lý HTTP tiêu chuẩn—GET, POST, PUT, DELETE—vì vậy việc xây dựng một back end REST song song với các trang của bạn sẽ cảm thấy rất tự nhiên.
Một tính năng thường bị đánh giá thấp: nhóm các route (group routes). Bằng cách bao bọc tên thư mục trong dấu ngoặc đơn, chẳng hạn như (auth), bạn tạo ra một layout dùng chung mà không thêm một phân đoạn nào vào URL. Điều này hoàn hảo cho các trang đăng nhập và đăng ký cần cùng một khung giao diện tối giản nhưng lại nằm ở /login và /signup, chứ không phải /auth/login.
Dữ liệu, bảo mật và cải tiến lũy tiến (progressive enhancement)
Các framework hiện đại rất thích nói về full-stack, nhưng nhiều cái khiến bạn phải đoán mò nơi đặt các kiểm tra xác thực hoặc logic form. SvelteKit cung cấp cho bạn các hook rõ ràng.
Sử dụng +page.server.ts để lấy dữ liệu (data fetching). Hàm load ở đó chỉ chạy duy nhất trên máy chủ, vì vậy thông tin đăng nhập cơ sở dữ liệu của bạn sẽ không bao giờ bị rò rỉ ra trình duyệt. SvelteKit tạo ra các kiểu dữ liệu từ các giá trị trả về của hàm load, giúp frontend của bạn luôn chính xác.
Sử dụng hooks.server.ts để kiểm soát toàn bộ ứng dụng. Nó chạy trên mỗi yêu cầu (request), khiến nó trở thành nơi phù hợp để xác minh phiên làm việc (session), kiểm tra thời hạn JWT hoặc đính kèm ngữ cảnh người dùng vào các sự kiện đang đến.
Đối với các thao tác thay đổi dữ liệu (mutations), hãy sử dụng form actions. Thay vì phải kết nối một endpoint API riêng biệt và xử lý JSON, bạn định nghĩa một action bên trong +page.server.ts. Điểm hay ở đây là khả năng cải tiến lũy tiến (progressive enhancement). Nếu JavaScript không tải được—hoặc nếu người dùng tắt nó—form vẫn sẽ gửi đến server action và trang sẽ được render lại với kết quả. Nếu có JavaScript, SvelteKit sẽ nâng cao trải nghiệm mà không cần tải lại toàn bộ trang. Bạn có được cả sự bền bỉ và sự mượt mà từ cùng một đoạn mã.
Một quy tắc cần ghi nhớ: hãy thực hiện các phép tính trong $derived, đừng làm trong $effect. Sử dụng $effect để tính toán giá trị có thể kích hoạt các vòng lặp cập nhật (update loops) rất khó truy vết. Hãy giữ $effect cho các tác dụng phụ thực sự, và để $derived đảm nhận trạng thái tính toán của bạn.
SvelteKit so với Next.js
Cả hai framework đều có thể triển khai các ứng dụng production, nhưng những sự đánh đổi là rất rõ rệt.
Kích thước bundle nghiêng về phía SvelteKit. Vì Svelte biên dịch các component thành vanilla JavaScript và bỏ qua hoàn toàn Virtual DOM, nên dung lượng runtime vẫn rất nhỏ. Next.js phải mang theo cả engine reconciliation của React.
Tính reactivity cũng khác nhau. SvelteKit giải quyết các runes tại thời điểm compile time. Trình duyệt sẽ nhận được các bản cập nhật thuần túy. Next.js dựa vào các runtime hooks và reconciliation của React, điều này đồng nghĩa với việc có nhiều tác vụ hơn phải xử lý ở phía client.
Việc tiếp cận (onboarding) với SvelteKit nhẹ nhàng hơn. Mô hình tư duy (mental model) cũng đơn giản hơn. Bạn không cần phải xoay xở với các dependency arrays của useEffect hay các bài toán memoization để tránh re-renders. Khả năng tích hợp TypeScript cũng là một điểm đáng lưu ý. Trong khi cả hai framework
