Có một sự thật phũ phàng mà nhiều agency phải đối mặt: sau khi nhận bàn giao website từ developer, đội SEO phát hiện phần lớn lỗi không thể sửa được nếu không refactor code.
Lý do rất đơn giản: SEO không phải một tầng "thêm vào" sau khi website xong — nó phải được tích hợp ngay từ bước kiến trúc. Một SPA React không có Server-Side Rendering, một trang sản phẩm load nội dung qua fetch(), một URL dạng /page?id=12345... đều là quyết định kỹ thuật ảnh hưởng trực tiếp tới khả năng SEO.
Google hiểu điều này nên có riêng một Developer's Guide. Bài viết này phân tích chi tiết, kèm code thực tế cho stack hiện đại.
Bài dành cho: frontend/full-stack developer, tech lead, solution architect, product owner cần hiểu yêu cầu kỹ thuật SEO, và marketing team cần "nói cùng ngôn ngữ" với dev.
Phần 1: Bốn trụ cột nền tảng
Ngoài các yêu cầu SEO cụ thể, Google yêu cầu website phải đạt bốn tiêu chuẩn nền:
Trụ cột | Ý nghĩa |
|---|---|
Secure | HTTPS, không lỗ hổng bảo mật |
Fast | Tốc độ tốt (Core Web Vitals) |
Accessible | Truy cập được cho mọi người (WCAG) |
Mobile-first | Hoạt động tốt trên mọi thiết bị |
Đây không phải "nice-to-have" — thiếu một trong bốn trụ cột này, website sẽ gặp khó khăn lớn về SEO bất kể nội dung tốt đến đâu. Đó cũng là lý do checklist Technical SEO luôn bắt đầu từ nền tảng kỹ thuật.
Phần 2: Google "nhìn" website của bạn ra sao?
Đây là bước quan trọng nhất — vì Google không phải lúc nào cũng thấy những gì bạn thấy trong browser.
Google minh họa bằng một ví dụ kinh điển: người dùng mở trang và thấy đầy đủ hình ảnh; nhưng Googlebot mở cùng trang đó lại chỉ thấy icon loading — vì trang dùng một tính năng JavaScript mà Google không hỗ trợ.
Công cụ kiểm tra: Search Console → URL Inspection → dán URL → "Test live URL" → "View tested page" → tab Screenshot. Đây chính là cách Google thực sự nhìn trang của bạn. Hoặc dùng Rich Results Test nếu muốn nhanh hơn (không cần verify Search Console).
Thói quen nên có: sau mỗi lần deploy thay đổi lớn, chạy URL Inspection cho các trang quan trọng. Rất nhiều lỗi chỉ lộ ra ở đây.
Phần 3: Links — Đường đi cho Googlebot
Googlebot điều hướng từ URL này sang URL khác bằng cách fetch và parse các link, sitemap và redirect. Quan trọng: Googlebot xem mỗi URL như thể đó là URL đầu tiên và duy nhất nó từng thấy từ website của bạn.
Nghĩa là: mọi trang quan trọng đều PHẢI có một con đường (link) dẫn tới nó.
Dùng thẻ <a href> mà Google crawl được
html
<!-- ĐÚNG - crawlable -->
<a href="/san-pham/ao-thun">Áo thun</a>
<a href="https://shop.com/san-pham/ao-thun">Áo thun</a>html
<!-- SAI - Google KHÔNG crawl được -->
<span onclick="window.location='/san-pham'">Sản phẩm</span>
<button onclick="navigate('/san-pham')">Xem</button>
<a onclick="goToProduct()">Sản phẩm</a> <!-- thiếu href -->
<a href="javascript:void(0)" onclick="loadPage()">Sản phẩm</a>
<div class="link" data-href="/san-pham">Sản phẩm</div>Với React Router / Vue Router
javascript
// SAI - không có href thật
<div onClick={() => navigate('/about')}>About</div>
// ĐÚNG - render ra <a href="/about">
<Link to="/about">About</Link> // React Router
<Link href="/about">About</Link> // Next.js
<router-link to="/about">About</router-link> // Vue RouterQuy tắc vàng: luôn dùng component Link chính thức của framework — nó render ra thẻ <a href> đúng chuẩn.
Mọi trang phải có đường dẫn tới nó
Google yêu cầu: đảm bảo mọi trang trên site có thể tiếp cận được bằng một link từ một trang khác đã tìm thấy được. Nghĩa là: không có orphan page; mọi sản phẩm link được từ danh mục; mọi bài blog link được từ trang blog.
Anchor text phải có nghĩa
html
<!-- TỐT -->
<a href="/seo-co-ban">Hướng dẫn SEO cơ bản</a>
<a href="/san-pham/ao-thun">
<img src="ao-thun.jpg" alt="Áo thun nam cotton 100%">
</a>
<!-- XẤU -->
<a href="/seo-co-ban">Click here</a>
<a href="/san-pham/ao-thun"><img src="ao-thun.jpg" alt=""></a>SPA chỉ có một file HTML — Chú ý đặc biệt
Google yêu cầu rõ: mỗi màn hình hoặc mỗi mảnh nội dung riêng biệt phải có một URL.
Không được: routing chỉ dùng hash (/#/products), hoặc toàn bộ app chỉ nằm ở https://app.com/ với nội dung load động.
Phải có URL thật cho từng state (/products, /products/1, /about) — dùng History API (pushState) hoặc router của framework.
Phần 4: JavaScript — Con dao hai lưỡi
Tin tốt: Google có thể chạy JavaScript khi crawl. Tin không tốt: Google không xử lý JS như browser thông thường:
Crawl hai giai đoạn: initial crawl (HTML) → rendering queue → index. Có thể delay vài ngày.
Không hỗ trợ mọi Web API.
Không tương tác — không click, không scroll, không hover.
Timeout nếu render quá lâu.
Chiến lược | Mô tả | SEO | Hiệu năng | Phù hợp với |
|---|---|---|---|---|
CSR | Browser render hoàn toàn | Rủi ro | First load chậm | Web app nội bộ, không cần SEO |
SSR | Server render mỗi request | Tốt | Phụ thuộc server | Site có nội dung động, cần SEO |
SSG | Render lúc build | Tốt nhất | Cực nhanh | Blog, marketing site, docs |
ISR | SSG + revalidate | Tốt | Nhanh | Ecommerce vừa và lớn |
Khuyến nghị: blog/website giới thiệu → SSG (Next.js, Nuxt, Astro); ecommerce → SSR hoặc ISR; web app phức tạp → SSR cho trang public, CSR cho dashboard.
Lazy loading — cẩn thận:
javascript
// SAI: lazy load nội dung above-the-fold
const PageTitle = lazy(() => import('./PageTitle'));
// ĐÚNG: chỉ lazy load nội dung below-the-fold
const Comments = lazy(() => import('./Comments'));Hydration mismatch — lỗi kinh điển khi dùng SSR:
javascript
// SAI - server và client render khác nhau
function Component() {
const isClient = typeof window !== 'undefined';
return <div>{isClient ? 'Client' : 'Server'}</div>;
}
// ĐÚNG - dùng useEffect
function Component() {
const [isClient, setIsClient] = useState(false);
useEffect(() => setIsClient(true), []);
return <div>{isClient ? 'Client' : 'Server'}</div>;
}Phần 5: Text vẫn là vua
Google nói rất rõ: Googlebot chỉ tìm thấy được nội dung hiển thị dưới dạng văn bản. Ví dụ, text nằm trong video là vô hình với Googlebot.
Google KHÔNG thấy: text trong video (chưa có caption), text trong hình ảnh, text trong <canvas>, text chỉ render khi tương tác (hover, click). Google THẤY: text trong DOM, alt text, caption/subtitle, text trong PDF.
html
<!-- XẤU: chỉ có hình, không có text -->
<div class="products">
<img src="ao1.jpg">
<img src="ao2.jpg">
</div>
<!-- TỐT: hình kèm text mô tả -->
<article>
<a href="/ao-1">
<img src="ao1.jpg" alt="Áo thun nam cotton trắng">
<h3>Áo thun nam cotton trắng</h3>
<p>Chất liệu cotton 100%, form regular</p>
<span class="price">299.000đ</span>
</a>
</article>Cảnh báo quan trọng: CSS content KHÔNG được index
Google nói rõ: nội dung thêm qua thuộc tính content của CSS không thuộc DOM, và Google Search bỏ qua nó.
css
/* Google KHÔNG thấy text này */
.warning::before {
content: "Cảnh báo: Sản phẩm có chứa gluten";
}html
<!-- ĐÚNG: text nằm trong DOM -->
<div class="warning">
<span class="icon" aria-hidden="true"></span>
Cảnh báo: Sản phẩm có chứa gluten
</div>Quy tắc: CSS content chỉ dùng cho trang trí (icon, dấu hiệu visual), không cho text có ý nghĩa SEO.
Title và meta description độc nhất
Mỗi trang cần title mô tả và meta description độc nhất — điều này giúp Google hiển thị trang của bạn phù hợp hơn với người dùng.
javascript
// Next.js App Router
export async function generateMetadata({ params }) {
const product = await getProduct(params.id);
return {
title: `${product.name} - Mua online giá tốt | Shop XYZ`,
description: product.shortDescription.slice(0, 155),
};
}javascript
// Nuxt 3
useSeoMeta({
title: `${product.value.name} - Mua online giá tốt | Shop XYZ`,
description: product.value.shortDescription.slice(0, 155),
});Độ dài tối ưu: title 50–60 ký tự; description 150–160 ký tự.
Semantic HTML
html
<!DOCTYPE html>
<html lang="vi">
<body>
<header>
<nav>
<ul><li><a href="/">Trang chủ</a></li></ul>
</nav>
</header>
<main>
<article>
<h1>Tiêu đề chính</h1>
<section>
<h2>Phần 1</h2>
<p>Nội dung...</p>
</section>
</article>
<aside>
<h3>Bài viết liên quan</h3>
</aside>
</main>
<footer>...</footer>
</body>
</html>Các thẻ quan trọng: <header>, <nav>, <main>, <article>, <section>, <aside>, <footer>; chỉ một <h1> mỗi trang; <figure>/<figcaption> cho ảnh có chú thích; <time> cho ngày tháng.
Phần 6: Quản lý nhiều phiên bản nội dung
Canonical khi nhiều URL cùng nội dung:
html
<!-- Trên /san-pham/ao?color=red -->
<link rel="canonical" href="https://example.com/san-pham/ao">Hreflang cho website đa ngôn ngữ:
html
<link rel="alternate" hreflang="vi" href="https://example.com/vi/">
<link rel="alternate" hreflang="en" href="https://example.com/en/">
<link rel="alternate" hreflang="x-default" href="https://example.com/">Lưu ý: phải có return tag (mỗi trang khai báo về các trang khác và cả chính nó); mã ngôn ngữ theo ISO 639-1.
Phần 7: Kiểm soát Google thấy gì
Ba cách block, mỗi cách công dụng khác nhau:
Cách | Khi nào dùng |
|---|---|
Yêu cầu đăng nhập/mật khẩu | Nội dung thực sự nhạy cảm, dữ liệu cá nhân, nội dung trả phí |
robots.txt | Không muốn Google tốn crawl budget cho trang không cần thiết |
| Cho Google crawl (vì có link bên trong) nhưng không hiển thị trang trong kết quả |
# robots.txt
User-agent: *
Disallow: /admin/
Disallow: /api/
Disallow: /cart/
Disallow: /checkout/
Sitemap: https://example.com/sitemap.xmlCảnh báo quan trọng nhất: robots.txt KHÔNG phải cơ chế để giữ một trang ra khỏi Google. Muốn trang không xuất hiện, hãy dùng
noindexhoặc bảo vệ bằng mật khẩu. Trang bị chặn bởi robots.txt vẫn có thể xuất hiện trong kết quả tìm kiếm (chỉ là Google không biết bên trong có gì).
Lỗi kết hợp rule — cực kỳ phổ biến
Google cảnh báo: kết hợp nhiều rule crawl và index có thể khiến rule này triệt tiêu rule kia.
# robots.txt
Disallow: /private-pagehtml
<!-- /private-page -->
<meta name="robots" content="noindex">Vấn đề: vì robots.txt đã chặn crawl, Google không bao giờ đọc được thẻ noindex → trang vẫn có thể xuất hiện. Đúng: nếu muốn noindex, phải CHO PHÉP crawl để Google đọc được thẻ đó.
Debug khi không index được
bash
# Kiểm tra Googlebot trong server log
grep "Googlebot" /var/log/nginx/access.log | tail -100
# Tìm lỗi 5xx khi Googlebot crawl
grep "Googlebot" /var/log/nginx/access.log | grep " 5[0-9][0-9] "
# Test bằng user-agent Googlebot
curl -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/pagePhần 8: Bật Rich Results
Rich result có thể gồm styling, hình ảnh hoặc tính năng tương tác giúp site bạn nổi bật hơn trong kết quả tìm kiếm. Format khuyến nghị: JSON-LD.
json
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Hướng dẫn SEO cho Developer",
"image": "https://example.com/article.jpg",
"datePublished": "2026-05-19T08:00:00+07:00",
"dateModified": "2026-05-19T08:00:00+07:00",
"author": { "@type": "Person", "name": "Tên tác giả" },
"publisher": {
"@type": "Organization",
"name": "Tên công ty",
"logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" }
}
}javascript
// Next.js: nhúng JSON-LD trực tiếp trong component
export default function ProductPage({ product }) {
const jsonLd = { '@context': 'https://schema.org', '@type': 'Product', name: product.name };
return (
<section>
<script type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
</section>
);
}Schema | Dùng cho | Lợi ích |
|---|---|---|
| Blog, tin tức | Hiện ngày, tác giả, ảnh |
| Ecommerce | Giá, đánh giá, tình trạng hàng |
| Cửa hàng, dịch vụ địa phương | Xuất hiện trong Local Pack |
| Trang FAQ | FAQ accordion trong kết quả |
| Mọi site | Breadcrumb thay vì URL thô |
| Site có video | Thumbnail, thời lượng |
| Trang công ty | Knowledge panel |
LUÔN validate bằng Rich Results Test và Schema Markup Validator trước khi deploy.
Phần 9: Tám anti-pattern phổ biến của developer Việt
Anti-pattern | Vấn đề | Giải pháp |
|---|---|---|
SPA không SSR cho site public | Build bằng Create React App → Google không thấy nội dung | Migrate sang Next.js/Astro với SSG |
URL vô nghĩa ( | Google và người dùng đều không hiểu |
|
Nội dung load qua AJAX sau khi trang ready | Google thấy skeleton rỗng | SSR/SSG nội dung quan trọng; AJAX chỉ cho phần phụ |
Lazy load ảnh hero (banner chính) | LCP tệ | Ảnh above-the-fold dùng |
Title/meta giống nhau mọi trang | Hardcode "Shop XYZ" cho tất cả | Template động theo nội dung trang |
Quên hreflang khi làm đa ngôn ngữ | Google không biết | Khai báo hreflang đầy đủ, có return tag |
Chặn JS/CSS trong robots.txt | Google không render được trang | Cho phép crawl JS/CSS |
Infinite scroll không có fallback phân trang | Google chỉ thấy 10 bài đầu | Có URL phân trang ( |
Phần 10: Checklist cho Developer
Giai đoạn | Cần làm |
|---|---|
Kiến trúc | Chọn render strategy (CSR/SSR/SSG/ISR); thiết kế cấu trúc URL có nghĩa; plan đa ngôn ngữ + hreflang; plan canonical, robots.txt, sitemap, structured data |
Phát triển | Framework có SSR/SSG; semantic HTML; dùng |
Trước launch | Sinh XML sitemap tự động; tạo robots.txt; setup Search Console + GA4; test URL Inspection cho trang quan trọng; validate structured data; test tốc độ và accessibility |
Sau launch | Submit sitemap; request indexing cho trang quan trọng; theo dõi server log (Googlebot); theo dõi lỗi trong Search Console; setup uptime + error tracking |
Kết luận
Đầu tư SEO ngay từ giai đoạn development không phải chi phí phụ — đó là khoản đầu tư ROI cực cao: tiết kiệm hàng trăm giờ refactor, có cơ hội rank ngay từ ngày launch, và cho người dùng trải nghiệm tốt (SEO chỉ là hệ quả).
Ba nguyên tắc cho developer:
SEO là kỹ thuật, không phải ma thuật. Mọi yêu cầu SEO đều có lý do kỹ thuật rõ ràng. Hiểu lý do → biết cách triển khai đúng.
Hãy giả định Googlebot là một browser cũ, chậm, không tương tác. Nếu trang của bạn vẫn hoạt động trong điều kiện đó, SEO sẽ ổn.
Test, test và test. URL Inspection, Rich Results Test, Lighthouse, PageSpeed Insights — tất cả đều miễn phí. Dùng chúng thường xuyên.
Nếu bạn là Tech Lead hoặc Solution Architect: hãy đưa SEO requirements vào tài liệu kỹ thuật ngay từ đầu dự án — đừng để tới phase QA mới phát hiện vấn đề.
SEO hiện đại không còn là việc riêng của marketer — mà là sự kết hợp giữa nội dung, kỹ thuật và trải nghiệm người dùng. Nếu bạn cần xây dựng website chuẩn SEO ngay từ kiến trúc kỹ thuật, hãy liên hệ Tấn Phát Digital để được tư vấn.
Biên soạn và Việt hóa từ tài liệu Google Search Central "Developer's guide to Search". Code example, anti-pattern và checklist thuộc về Tấn Phát Digital.















