Xây Dựng Web App Hiện Đại với SvelteKit: Từ Cài Đặt Đến Deploy Production

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

Tại Sao SvelteKit Trở Thành Framework Yêu Thích Của Tôi

Vài năm trước tôi đang bảo trì một ứng dụng React tầm trung cho khách hàng. Bundle size cứ tăng dần, lỗi hydration xuất hiện ngẫu nhiên, và mỗi developer mới vào nhóm cần cả tuần chỉ để hiểu cách setup state management. Một đồng nghiệp gợi ý tôi thử SvelteKit cho dự án tiếp theo. Tôi hoài nghi lắm — lại thêm một JavaScript framework hiện đại nữa? Nhưng sau một cuối tuần thử nghiệm, tôi không bao giờ quay lại nữa.

SvelteKit biên dịch các component thành vanilla JavaScript lúc build. Không có virtual DOM, không có runtime framework overhead, và mô hình tư duy đơn giản đến mức sảng khoái. Bạn viết các file .svelte trông giống HTML được nâng cấp, và compiler xử lý phần còn lại. Với server-side rendering, static generation và API routes, tất cả đều nằm trong một project với hệ thống routing theo file — hoàn toàn hợp lý.

Ba tháng sau, dự án mới đã ra mắt. Build output chỉ dưới 80KB cho entry bundle — so với 340KB của ứng dụng React mà nó thay thế — và thành viên mới làm việc hiệu quả trong một ngày, không phải một tuần. SvelteKit bao phủ full stack mà không ép bạn vào một hệ sinh thái phức tạp ngay từ đầu.

Cài Đặt và Khởi Tạo Dự Án

Bạn cần Node.js 18 trở lên. Kiểm tra phiên bản trước:

node --version
npm --version

Khởi tạo dự án SvelteKit mới bằng CLI chính thức:

npm create svelte@latest my-sveltekit-app
cd my-sveltekit-app
npm install

CLI sẽ hỏi một vài câu. Với dự án thực tế, tôi thường chọn:

  • Template: Skeleton project (cho bạn một tờ giấy trắng sạch sẽ)
  • Type checking: TypeScript (đáng đầu tư cho bất kỳ dự án nào bạn sẽ bảo trì lâu dài)
  • Prettier + ESLint: Có — tránh tranh cãi khi code review

Khởi động dev server:

npm run dev

Mở http://localhost:5173 và bạn sẽ thấy trang mặc định. Lưu file và thay đổi xuất hiện ngay lập tức — dev server dùng hot module replacement của Vite, nên state ứng dụng không bị reset giữa các lần chỉnh sửa.

Cấu Trúc Dự Án Bạn Cần Nắm

Routing theo file là trung tâm của cách SvelteKit hoạt động. Thư mục src/routes ánh xạ trực tiếp tới các đường dẫn URL:

src/
├── routes/
│   ├── +page.svelte          ← hiển thị tại /
│   ├── about/
│   │   └── +page.svelte      ← hiển thị tại /about
│   ├── blog/
│   │   ├── +page.svelte      ← hiển thị tại /blog
│   │   └── [slug]/
│   │       └── +page.svelte  ← hiển thị tại /blog/bat-ky-slug-nao
│   └── api/
│       └── posts/
│           └── +server.ts    ← API endpoint tại /api/posts
├── lib/
│   └── components/           ← các component tái sử dụng
└── app.html                  ← template HTML gốc

Tiền tố + trên tên file là có chủ ý — nó phân biệt các file đặc biệt của SvelteKit với file của bạn trong cùng thư mục.

Cấu Hình: SSR, Adapter và Biến Môi Trường

File svelte.config.js là trái tim của cấu hình SvelteKit. Mặc định nó đi kèm adapter auto, hoạt động tốt cho hầu hết môi trường Node.js:

import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter()
  }
};

export default config;

Khi deploy lên production, bạn sẽ cần một adapter cụ thể. Các adapter phổ biến nhất:

  • @sveltejs/adapter-node — Node.js server (VPS, Docker)
  • @sveltejs/adapter-vercel — Vercel Edge/Serverless
  • @sveltejs/adapter-staticoutput hoàn toàn tĩnh (Nginx, CDN)

Chuyển sang Node adapter khi deploy lên VPS:

npm install -D @sveltejs/adapter-node
import adapter from '@sveltejs/adapter-node';

const config = {
  kit: {
    adapter: adapter({
      out: 'build'  // thư mục output
    })
  }
};

Xử Lý Biến Môi Trường

Một thiết kế khá thú vị: SvelteKit phân chia cứng giữa secret server và biến public. Trong file .env:

# Public — hiển thị cho browser
PUBLIC_API_URL=https://api.example.com

# Private — chỉ dùng phía server
DATABASE_URL=postgresql://user:pass@localhost/mydb
SECRET_KEY=your-secret-here

Truy cập trong code:

// Trong +page.server.ts hoặc +server.ts (chỉ phía server)
import { DATABASE_URL } from '$env/static/private';

// Trong +page.svelte (an toàn cho client)
import { PUBLIC_API_URL } from '$env/static/public';

SvelteKit báo lỗi build nếu bạn vô tình import biến private vào code phía client. Cơ chế bảo vệ này đã cứu tôi khỏi việc lộ API key không ít lần.

Xây Dựng Trang Đầu Tiên Với Data Loading

Một trong những pattern gọn gàng nhất của SvelteKit là hàm load. Tạo trang danh sách blog — tương tự cách các REST API Node.js trả dữ liệu về cho frontend:

// src/routes/blog/+page.server.ts
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ fetch }) => {
  const response = await fetch('/api/posts');
  const posts = await response.json();
  return { posts };
};
<!-- src/routes/blog/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';
  export let data: PageData;
</script>

<h1>Blog</h1>
{#each data.posts as post}
  <article>
    <h2><a href="/blog/{post.slug}">{post.title}</a></h2>
    <p>{post.excerpt}</p>
  </article>
{/each}

Dữ liệu chảy từ server xuống component với đầy đủ TypeScript types được tạo tự động. Không Redux, không wiring context API, không prop drilling qua ba lớp component.

Build và Deploy Lên Production

Build production bundle:

npm run build

# Xem trước bản build production ở local
npm run preview

Với Node adapter, thư mục build/ chứa một Node.js server độc lập. Deploy bằng:

# Trên server của bạn
node build/index.js

Hoặc tạo một Dockerfile đơn giản:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY build ./build
EXPOSE 3000
CMD ["node", "build/index.js"]
docker build -t my-sveltekit-app .
docker run -p 3000:3000 -e PORT=3000 my-sveltekit-app

Đặt biến môi trường PORT để kiểm soát cổng server lắng nghe. Đằng sau Nginx, thêm block reverse proxy:

server {
  listen 80;
  server_name yourdomain.com;

  location / {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}

Kiểm Tra và Giám Sát

Sau khi deploy, xác nhận SSR đang hoạt động. Gửi request đến trang bằng curl và kiểm tra HTML có được pre-render không:

curl -s https://yourdomain.com/blog | grep -o '<h1>.*</h1>'

Nếu bạn thấy heading trong response HTML thô — không phải container <div> rỗng — SSR đang hoạt động tốt. Body trống nghĩa là trang đang render hoàn toàn phía client. Kiểm tra lại các hàm load và cấu hình adapter.

Đo Hiệu Năng Cơ Bản

Chạy kiểm tra Lighthouse nhanh từ command line:

npx lighthouse https://yourdomain.com --output json --quiet | \
  node -e "const d=require('fs').readFileSync('/dev/stdin','utf8'); \
           const r=JSON.parse(d); \
           console.log('Hiệu năng:', r.categories.performance.score * 100);"

Điểm hiệu năng 90–98 là chuyện bình thường khi mới deploy. Không có runtime framework nào cần tải — chỉ là code component đã biên dịch cộng với những gì bạn tự import.

Logging và Theo Dõi Lỗi

Error hook nằm trong src/hooks.server.ts — có sẵn, không cần cài thêm package nào:

import type { HandleServerError } from '@sveltejs/kit';

export const handleError: HandleServerError = ({ error, event }) => {
  console.error('Lỗi server:', error, 'tại đường dẫn:', event.url.pathname);
  // Gửi đến Sentry, Datadog, v.v.
  return {
    message: 'Đã có lỗi xảy ra. Chúng tôi đã được thông báo.'
  };
};

Để giám sát production, trỏ PM2 vào build output. Nếu cần ghi nhật ký tập trung cho nhiều service, Grafana Loki là lựa chọn nhẹ và hiệu quả để tổng hợp log từ nhiều nguồn:

npm install -g pm2
pm2 start build/index.js --name sveltekit-app -i max
pm2 save
pm2 startup

Flag -i max chạy một instance cho mỗi CPU core — scale ngang mà không cần thay một dòng code nào trong ứng dụng.

Sau hai năm ship các dự án SvelteKit, điều tôi cứ quay lại là ít thứ phải vật lộn. API surface nhỏ, tài liệu tốt, và dev server chạy trên Vite vẫn nhanh khi dự án lớn dần. Nếu framework hiện tại của bạn bắt đầu cảm giác như gánh nặng hơn là công cụ, hãy dành một cuối tuần thực sự thử nó. Sự so sánh tự nói lên tất cả.

Share: