Trước khi đi vào bài viết, mình xin kính chúc mọi người một năm mới tràn đầy hạnh phúc, an khang, thịnh vượng ♥️♥️♥️
Với sự phát triển của công nghệ thì việc xây dựng một website vừa SEO tốt (SSR) vừa mượt mà (SPA) đã rất đơn giản với sự hổ trợ tận răng của các super framework như Next.js hay Nuxt.js. Tuy nhiên trong bài này chúng ta sẽ làm cho một website SSR truyền thống trở nên mượt mà hơn mà không sử dụng Next hay Nuxt.
Một số khái niệm (ngắn gọn)
SSR
SSR (Server-Side Rendering) là server sẽ tạo các nội dung của trang (HTML, CSS, JS) và gửi chúng đến trình duyệt để hiển thị. Điều này giúp tải trang nhanh, SEO tốt, tuy nhiên lúc chuyển trang sẽ "khựng" một chút do lại phải request đến server để render (tải lại trang).
CSR
CSR (Client-Side Rendering) ngược lại sẽ render nội dung ở phía client (trình duyệt), server chỉ cần gửi html chứa các tài nguyên CSS, JS cần thiết, sau đó trình duyệt sẽ sử dụng javascript để tải dữ liệu và render các nội dung của trang. Việc này sẽ khiến SEO không tốt do html ban đầu chưa có nội dung gì.
SPA
SPA (Single Page Application) là một loại ứng dụng web mà tất cả các trang và nội dung của ứng dụng được tải một lần duy nhất trong quá trình tải trang ban đầu. Thay vì tải lại trang khi người dùng điều hướng hoặc thực hiện hành động, SPA chỉ cần thay đổi nội dung trang bằng cách thực thi mã JavaScript, mà không cần tải lại toàn bộ trang tạo ra trải nghiệm tương tác mượt mà.
Kết hợp SSR và CSR trong SPA
Kết hợp SSR và CSR là kết hợp ưu điểm của cả hai, giúp SPA của bạn vừa SEO tốt hơn, vừa có trả nghiệm mượt mà. Các framework như Next.js, Nuxt.js đều mặc định hỗ trợ việc này với các tên gọi khác nhau như SSR, Dynamic Rendering (Next.js) hoặc Universal Rendering (Nuxt.js). Tuy nhiên như đã đề cập ở trên, chúng ta sẽ không dùng các framework trên mà chỉ sử dụng Vanilla JS (Javascript thuần).
Trước tiên chúng ta sẽ cần một trang web SSR truyền thống, tạo bới Laravel, RoR hay Nestjs chẳng hạn. Tạo một file js và bắt đầu coding thôi
Lắng nghe sự kiện khi click vào nội dung trên trang
Khi click vào các internal link trong trang thì preventDefault để trang web không load lại mà chúng ta sẽ tự xử lý.
function getLinkTarget(target) { while (target && target.nodeName != 'A') { target = target.parentNode; } return target; } function click(e) { // Lấy phần tử A từ phần tử được click var a = getLinkTarget(e.target); // Nếu không phải phần tử A thì không làm gì, nên kiểm tra thêm các // điều kiện khác như là external link, link có attribute download... if (!a) { return; } // mở link trong tab mới thì không làm gì if (e.which > 1 || e.metaKey || e.ctrlKey) { return; } // event thỏa mản điều kiện thì preventDefault để trình duyệt không load lại trang // và thực hiện display trang mới e.preventDefault(); loadPage(a.href); } // Lắng nghe sự kiện click trên toàn bộ trang document.body.addEventListener('click', click, true);
Get nội dung của link được click bằng XMLHttpRequest
Dùng xhr(hoặc fetch) để get nội dung của của page mới
var $xhr = new XMLHttpRequest(); function loadPage(url) { // Dùng XMLHttpRequest để get nội dung trang web $xhr.open("GET", url); $xhr.send(); // Nếu trang web trả về HTML, lấy title và body của trang web if ($xhr.getResponseHeader("Content-Type").match(/\/(x|ht|xht)ml/)) { var doc = document.implementation.createHTMLDocument(""); doc.documentElement.innerHTML = $xhr.responseText; var title = doc.title; var body = doc.body; // Hiển thị page đã get displayPage(title, body, url) }
}
Hiển thị page đã get
Sau khi đã get được nội dung của page mới, dùng javascript để thay đổi nội dung, title của page hiện tại bằng data mới.
function displayPage(title, body, newUrl) { // Thay thế body bằng body mới đã get được bằng xhr document.documentElement.replaceChild(body, document.body); // Sử dụng History API để thay đổi url trên thanh địa chỉ history.pushState(null, null, newUrl); // Scroll về đầu trang khi chuyển sang trang mới scrollTo({ left: 0, top: offset, behavior: 'instant', }); // Thay đổi title thành title mới document.title = title;
}
Thực hiện chạy các file script trên trang mới
Cuối cùng, khi thay đổi nội dung trang web bằng hàm replaceChild()
ở bước bên trên thì các script trên trang mới không được tự động thực thi, nên ta cần bắt nó phải chạy.
Bạn có thể thêm logic để chỉ định những script chỉ cần chạy 1 lần và không cần chạy lại khi chuyển trang.
var scripts = document.body.getElementsByTagName("script"), script, copy, parentNode, nextSibling; for (i = 0, j = scripts.length; i < j; i++) { script = scripts[i]; copy = document.createElement("script"); if (script.src) { copy.src = script.src; } if (script.innerHTML) { copy.innerHTML = script.innerHTML; } parentNode = script.parentNode; nextSibling = script.nextSibling; parentNode.removeChild(script); parentNode.insertBefore(copy, nextSibling);
}
Như vậy là chúng ta đã hoàn thành việc biến một website SSR trở thành một SPA, giúp trang web của bạn mượt mà hơn. Trên đây chỉ là các bước cơ bản, để hoàn thiện hơn thì bạn sẽ cần code nhiều hơn, như thêm progress bar chẳng hạn...
Lưu ý: giữa các page cần dùng chung css, nếu mỗi page dùng css riêng thì bạn cần code thêm để load css mới nhé. Lưu ý 2:
Demo
- Framework: Nest.js (MVC)
- Link: Giarevn.com
Do chủ yếu chỉ có HTML và một ít js (do không dùng các framework nặng) và nội dung cũng không có gì 🤣 nên được chấm full điểm PageSpeed.