Cái giá tiềm ẩn của các ứng dụng Web có khả năng mở rộng
Vào năm 2021, tôi đã dẫn dắt một nhóm xây dựng một nền tảng thương mại điện tử quy mô lớn. Chúng tôi chọn kiến trúc Single Page Application (SPA) tiêu chuẩn sử dụng Vue 2. Trên một chiếc MacBook cấu hình cao, trang web chạy mượt như nhung. Nhưng khi đưa vào production, thực tế phũ phàng ập đến với điểm SEO Lighthouse chỉ đạt 45/100 và chỉ số First Contentful Paint (FCP) mất tới 5 giây trên các thiết bị Android tầm trung.
Thứ hạng tìm kiếm của chúng tôi gần như bằng không. Vì ứng dụng hoàn toàn chạy trên trình duyệt, các trình thu thập dữ liệu (crawler) của Google không thấy gì ngoài một thẻ <div id="app"></div> trống rỗng. Người dùng sử dụng kết nối 3G phải nhìn chằm chằm vào màn hình trắng trong vài giây trong khi một gói JavaScript bundle nặng 2.5MB được tải xuống và phân tích. Lúc đó, chúng tôi nhận ra rằng một SPA tiêu chuẩn không phải lúc nào cũng là công cụ phù hợp cho các sản phẩm tăng trưởng hướng tới công chúng.
Tại sao các SPA truyền thống gặp khó khăn khi mở rộng quy mô
Vấn đề nằm ngay trong mô hình Client-Side Rendering (CSR). Trong thiết lập này, máy chủ chỉ đóng vai trò là một nơi lưu trữ tệp đơn giản. Nó gửi một khung HTML rỗng và một thẻ script, để mặc thiết bị của người dùng xử lý mọi tác vụ nặng nề. Nếu người dùng có bộ vi xử lý yếu hoặc kết nối mạng chập chờn, ứng dụng của bạn sẽ tạo cảm giác như bị lỗi.
Việc bảo trì trở thành một trở ngại khác. **Options API** cũ trong Vue 2 buộc bạn phải tổ chức mã theo kiểu dữ liệu thay vì theo tính năng. Hãy tưởng tượng việc debug một giỏ hàng mà logic bị chia nhỏ giữa các dòng 50, 200 và 450 của cùng một tệp. Đó là công thức tạo ra nợ kỹ thuật (technical debt). Việc quản lý trạng thái (state management) qua Vuex chỉ làm tăng thêm sự rắc rối, thường yêu cầu tới bốn tệp khác nhau chỉ để cập nhật một chuỗi ký tự đơn giản.
Sự chuyển dịch: CSR vs. SSR vs. Composition API
Phát triển web hiện đại đã chuyển sang một cách tiếp cận cân bằng hơn. Dưới đây là cách mà bối cảnh công nghệ đã thay đổi:
- Vue 2 + Vuex (Cách làm cũ): Đáng tin cậy cho các công cụ nội bộ, nhưng là một điểm yếu về SEO. Việc tái sử dụng logic thường liên quan đến các Mixins lộn xộn, làm ẩn đi nguồn gốc của các thuộc tính (properties).
- Vue 3 + Composition API (Giải pháp cho Logic): Cho phép bạn nhóm mã theo chức năng. Bằng cách sử dụng
setupvàcomposables, chúng tôi đã thấy các nhóm giảm lượng mã lặp (boilerplate) trong component lên đến 40%. - Nuxt 3 (Giải pháp cho Hiệu năng): Nuxt hỗ trợ sẵn Server-Side Rendering (SSR). Nó tạo HTML trên máy chủ, nghĩa là người dùng có thể thấy nội dung ngay khi byte đầu tiên được truyền tới.
Một Stack đã được kiểm chứng: Nuxt 3, Composition API và Pinia
Sau khi di chuyển nhiều dự án doanh nghiệp, tôi nhận thấy sự kết hợp đặc biệt này mang lại sự cân bằng tốt nhất giữa tốc độ và trải nghiệm lập trình. Nó không chỉ là về các công cụ mới; mà là về một quy trình làm việc dễ dự đoán hơn. Hãy cùng xem cách triển khai.
1. Khởi tạo với Nuxt
Nuxt loại bỏ sự mệt mỏi khi phải cấu hình Vite hoặc Webpack thủ công. Bạn có thể có một môi trường sẵn sàng cho production chỉ trong vài giây:
npx nuxi@latest init my-modern-app
cd my-modern-app
npm install
Hãy quên việc quản lý một tệp router.js dài 500 dòng đi. Nuxt sử dụng hệ thống routing dựa trên tệp (file-based routing). Nếu bạn tạo pages/products/[id].vue, framework sẽ tự động tạo route động cho bạn. Cấu trúc này giúp dự án luôn ngăn nắp khi bạn mở rộng từ 10 lên hơn 100 route.
2. Logic sạch sẽ với <script setup>
Cú pháp <script setup> là cải tiến đáng kể nhất trong Vue 3. Nó giúp các component gọn nhẹ và dễ đọc hơn. Thay vì phải nhảy qua lại giữa các thuộc tính đối tượng khác nhau, bạn định nghĩa logic theo trình tự tuyến tính.
<script setup>
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
</script>
<template>
<div>
<p>Số đếm hiện tại: {{ count }}</p>
<p>Kết quả nhân đôi: {{ doubleCount }}</p>
<button @click="increment">Tăng số</button>
</div>
</template>
Cách tiếp cận này cho phép bạn chuyển các logic phức tạp vào các “Composables”. Hãy coi chúng là các hàm chuyên biệt, có thể tái sử dụng, giúp các UI component chỉ tập trung vào lớp hiển thị (view layer).
3. Quản lý State tinh gọn với Pinia
Pinia là người kế nhiệm nhẹ nhàng của Vuex. Nó loại bỏ nhu cầu về mutations, vốn luôn là một điểm gây khó chịu trong Vue 2. Cảm giác làm việc với Pinia giống như làm việc với các đối tượng JavaScript tiêu chuẩn trong khi vẫn giữ được lợi ích của một state toàn cục và có tính phản ứng (reactive).
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
name: 'Alex',
isLoggedIn: false
}),
actions: {
login(userName) {
this.name = userName;
this.isLoggedIn = true;
}
}
})
Truy cập store trong một component rất trực tiếp. Bạn không cần mapGetters hay mapActions nữa. Chỉ cần import store và sử dụng.
4. Lấy dữ liệu thông minh
Việc lấy dữ liệu (data fetching) trong Nuxt 3 được thiết kế để ngăn chặn vấn đề “double fetch”. Trong một SPA tiêu chuẩn, client thường yêu cầu dữ liệu mà lẽ ra server đã có thể cung cấp sẵn. Composable useFetch của Nuxt xử lý quá trình chuyển đổi này một cách mượt mà.
<script setup>
const { data: products, pending } = await useFetch('/api/products', {
lazy: true
})
</script>
<template>
<div v-if="pending">Đang cập nhật kho hàng...</div>
<ul v-else>
<li v-for="item in products" :key="item.id">{{ item.name }}</li>
</ul>
</template>
Khi một công cụ tìm kiếm truy cập trang này, máy chủ sẽ đợi phản hồi từ API và chèn kết quả trực tiếp vào HTML. Điều này đảm bảo nội dung của bạn được lập chỉ mục ngay lập tức.
Lựa chọn chiến lược triển khai (Deployment)
Nuxt sử dụng Nitro engine, nghĩa là bạn không bị bó buộc vào một nhà cung cấp hosting duy nhất. Bạn có ba con đường chính cho môi trường production:
- Static Generation (SSG): Hoàn hảo cho blog hoặc tài liệu. Sử dụng
npx nuxi generateđể xây dựng một trang web có thể chạy trên các CDN như Netlify với chi phí gần như bằng không. - Node.js truyền thống: Tốt nhất cho các ứng dụng động yêu cầu máy chủ duy trì liên tục. Bạn có thể chạy thư mục
.outputtrên bất kỳ VPS nào bằng trình quản lý tiến trình như PM2. - Edge/Serverless: Triển khai lên các nền tảng như Vercel hoặc Cloudflare Workers. Mã nguồn của bạn sẽ chạy ở vị trí địa lý gần với người dùng nhất, giảm độ trễ xuống mức tối thiểu.
Chuyển từ một ứng dụng Vue SPA tiêu chuẩn sang kiến trúc dựa trên Nuxt đòi hỏi một quá trình học hỏi, nhưng kết quả mang lại hoàn toàn xứng đáng. Bạn có được SEO tốt hơn, hiệu năng cảm nhận nhanh hơn và một cơ sở mã nguồn (codebase) không bị sụp đổ dưới sức nặng của chính nó. Bằng cách áp dụng các mô hình này, bạn đang xây dựng các ứng dụng web thực sự sẵn sàng cho kỷ nguyên internet hiện đại.

