Tan Phat Media

JavaScript SEO Là Gì? Cách SEO Website React & Vue

25 tháng 5, 2026
4.060
javascript seo
seo react
seo vue
seo angular
technical seo javascript
cách seo website react
javascript seo theo google
tối ưu seo cho react js
seo cho website javascript framework
JavaScript SEO Là Gì? Cách SEO Website React & Vue - Tấn Phát Digital

Phần lớn website hiện đại đều dựng trên JavaScript framework — React (Next.js), Vue (Nuxt), Angular, Svelte. Nhưng sự thật phũ phàng: rất nhiều Single-Page App (SPA) gặp vấn đề SEO nghiêm trọng. Google không thấy nội dung (vì tải qua JS), title/description không đổi giữa các trang, soft 404 tràn lan, routing không crawl được, structured data bị bỏ sót.

Bài viết này hướng dẫn frontend developer cách xây SPA vừa đẹp về UX, vừa thân thiện với Google. Bài dành cho: frontend developer (React, Vue, Angular), full-stack developer, technical SEO chuyên về JS site, và engineering manager quản lý team frontend.

Phần 1: Google xử lý JavaScript như thế nào?

Google xử lý web app JavaScript qua ba giai đoạn:

  1. Crawling — kiểm tra robots.txt, fetch HTML, phân tích các link <a href> và đưa vào crawl queue, rồi xếp trang vào hàng đợi render.

  2. Rendering — headless Chromium thực thi JavaScript, tạo ra HTML đã render.

  3. Indexing — phân tích HTML đã render, trích xuất nội dung và link, rồi index.

Bốn điểm cần nhớ:

  • Rendering tốn thời gian. Trang có thể nằm trong hàng đợi vài giây — nhưng cũng có thể lâu hơn nhiều. Render luôn chậm hơn crawl thông thường.

  • Server-side rendering vẫn tốt hơn. Google nói rõ SSR hoặc pre-rendering vẫn là ý tưởng hay vì nó làm website nhanh hơn cho cả người dùng lẫn crawler, và không phải bot nào cũng chạy được JavaScript.

  • Robots.txt vẫn áp dụng. Google sẽ không render JavaScript từ file hoặc trang bị chặn — đừng chặn JS/CSS trong robots.txt.

  • Status code quan trọng. Mọi trang trả về 200 OK mới được đưa vào hàng đợi render. Nếu status khác 200, việc render có thể bị bỏ qua.

Chiến lược render

SEO

Hiệu năng

Độ phức tạp

CSR (Client-Side)

Kém

Tải lần đầu chậm

Đơn giản

SSR (Server-Side)

Tốt

Tốt

Phức tạp

SSG (Static Generation)

Tốt nhất

Tốt nhất

Trung bình

ISR (Incremental Static)

Tốt nhất

Tốt nhất

Trung bình

Hybrid

Tốt

Tốt

Phức tạp nhất

Khuyến nghị: Next.js → SSG/ISR; Nuxt → SSR/SSG; SvelteKit → SSR; React thuần → tránh CSR nếu cần SEO.

Phần 2: Title và meta description động

Google xác nhận: bạn có thể dùng JavaScript để đặt hoặc thay đổi <title> và meta description. Điều quan trọng là mỗi trang phải có title/description độc nhất và mô tả đúng.

javascript

// React — react-helmet
import { Helmet } from 'react-helmet-async';

function ProductPage({ product }) {
  return (
    <Helmet>
      <title>{product.name} | Shop XYZ</title>
      <meta name="description" content={product.description} />
      <link rel="canonical" href={`https://shop.com/products/${product.slug}`} />
    </Helmet>
  );
}

javascript

// Next.js
import Head from 'next/head';

<Head>
  <title>{product.name} | Shop XYZ</title>
  <meta name="description" content={product.description} />
  <link rel="canonical" href={`https://shop.com/products/${product.slug}`} />
</Head>

javascript

// Nuxt 3
useHead({
  title: `${product.name} | Shop XYZ`,
  meta: [{ name: 'description', content: product.description }],
  link: [{ rel: 'canonical', href: `https://shop.com/products/${product.slug}` }]
});

typescript

// Angular
import { Title, Meta } from '@angular/platform-browser';

constructor(private titleService: Title, private metaService: Meta) {}

ngOnInit() {
  this.loadProduct().then(product => {
    this.titleService.setTitle(`${product.name} | Shop XYZ`);
    this.metaService.updateTag({ name: 'description', content: product.description });
  });
}

Phần 3: Canonical URL với JavaScript

Bạn có thể dùng JavaScript để đặt canonical URL — nhưng không nên dùng JS để đổi canonical thành một URL khác với URL đã khai báo trong HTML gốc.

Tốt nhất: đặt canonical ngay trong HTML gốc (server-side). Nếu buộc phải đặt bằng JS, chỉ được có duy nhất một canonical tag và không xung đột với HTML.

javascript

fetch('/api/cats/' + id)
  .then(response => response.json())
  .then(cat => {
    const linkTag = document.createElement('link');
    linkTag.setAttribute('rel', 'canonical');
    linkTag.href = 'https://example.com/cats/' + cat.urlFriendlyName;
    document.head.appendChild(linkTag);
  });

Phần 4: HTTP status code cho SPA — vấn đề Soft 404

Đây là lỗi phổ biến nhất của SPA: người dùng vào /product/id-khong-ton-tai, React Router render component lỗi — nhưng HTTP status vẫn là 200 OK. Google index trang lỗi đó. Đây chính là soft 404.

Giải pháp

Ưu điểm

Nhược điểm

Redirect sang trang 404 thật

Trả đúng status 404

Có thêm một lần redirect

Chèn thẻ noindex

Không cần redirect

Vẫn trả status 200

Khuyến nghị: dùng cách 1 (redirect) vì tốt hơn cho SEO — và đây cũng là nền tảng của chiến lược xử lý lỗi 404 đúng chuẩn.

javascript

// Cách 1: redirect về trang 404 do server trả về
fetch(`/api/products/${productId}`)
  .then(response => response.json())
  .then(product => {
    if (product.exists) {
      showProductDetails(product);
    } else {
      window.location.href = '/not-found';  // server trả HTTP 404
    }
  })
  .catch(() => { window.location.href = '/not-found'; });

javascript

// Next.js: trả 404 thật từ server
export async function getServerSideProps({ params }) {
  const product = await fetchProduct(params.id);
  if (!product) {
    return { notFound: true };   // → HTTP 404
  }
  return { props: { product } };
}

Phần 5: Dùng History API, KHÔNG dùng fragment (#)

Đây là quy tắc cực kỳ quan trọng. Google chỉ khám phá được link nếu chúng là thẻ <a> có thuộc tính href — và Google không resolve fragment (#), nên routing kiểu #/products sẽ không được crawl.

html

<!-- SAI: routing bằng fragment → Google không crawl được -->
<a href="#/products">Sản phẩm</a>

html

<!-- ĐÚNG: link thật + History API -->
<nav>
  <a href="/products">Sản phẩm</a>
  <a href="/services">Dịch vụ</a>
</nav>

<script>
function goToPage(event) {
  event.preventDefault();
  const hrefUrl = event.target.getAttribute('href');
  document.getElementById('placeholder').innerHTML = load(hrefUrl.slice(1));
  window.history.pushState({}, window.title, hrefUrl);  // cập nhật URL
}
document.querySelectorAll('a').forEach(link => link.addEventListener('click', goToPage));
</script>

Trong các framework:

javascript

// React Router v6 — BrowserRouter dùng History API, <Link> render ra <a href> crawlable
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

<BrowserRouter>
  <Link to="/products">Products</Link>
</BrowserRouter>

// TRÁNH: HashRouter → URL dạng /#/products → Google không crawl được

javascript

// Vue Router
import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
  history: createWebHistory(),        // ĐÚNG
  // history: createWebHashHistory(), // TRÁNH — dùng fragment
  routes: [/* ... */]
});

Angular Router mặc định đã dùng History API — không cần cấu hình thêm.

Phần 6: Thẻ robots meta với JavaScript

Cảnh báo quan trọng: khi Google gặp thẻ noindex, nó có thể bỏ qua việc render và thực thi JavaScript. Nghĩa là dùng JS để gỡ noindex có thể không hoạt động như bạn mong đợi.

Do đó: nếu muốn trang được index, đừng để noindex trong HTML gốc và đừng trông cậy vào JS để gỡ nó. Ngược lại, nếu cần noindex động (ví dụ trang lỗi), hãy để HTML gốc không có noindex và dùng JS thêm vào khi cần:

javascript

fetch('/api/products/' + productId)
  .then(response => response.json())
  .then(apiResponse => {
    if (apiResponse.isError) {
      let metaRobots = document.querySelector('meta[name="robots"]');
      if (!metaRobots) {
        metaRobots = document.createElement('meta');
        metaRobots.setAttribute('name', 'robots');
        document.head.appendChild(metaRobots);
      }
      metaRobots.setAttribute('content', 'noindex');
      errorMsg.textContent = 'Sản phẩm này không còn khả dụng';
      return;
    }
    displayProduct(apiResponse);
  });

Phần 7: Content Fingerprinting

Googlebot cache rất tích cực để giảm request mạng, và hệ thống render có thể bỏ qua caching header — dẫn tới việc dùng JS/CSS , render sai, và ảnh hưởng thứ hạng.

Giải pháp: content fingerprinting — nhúng hash của nội dung vào tên file, để khi nội dung đổi thì tên file cũng đổi, buộc phải fetch lại (main.jsmain.2bb85551.js).

javascript

// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].chunk.js',
  },
};

javascript

// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js',
        chunkFileNames: 'assets/[name].[hash].js',
        assetFileNames: 'assets/[name].[hash].[ext]'
      }
    }
  }
};

Next.js tự động fingerprint mọi asset — không cần cấu hình.

Phần 8: Structured Data với JavaScript

Google cho phép dùng JavaScript để sinh JSON-LD và inject vào trang:

javascript

fetch(`/api/products/${productId}`)
  .then(response => response.json())
  .then(product => {
    const schema = {
      "@context": "https://schema.org/",
      "@type": "Product",
      "name": product.name,
      "image": product.image,
      "description": product.description,
      "offers": {
        "@type": "Offer",
        "price": product.price,
        "priceCurrency": "VND",
        "availability": product.inStock
          ? "https://schema.org/InStock"
          : "https://schema.org/OutOfStock"
      }
    };

    const script = document.createElement('script');
    script.type = 'application/ld+json';
    script.text = JSON.stringify(schema);
    document.head.appendChild(script);
  });

Luôn kiểm tra bằng Rich Results Test và URL Inspection trong Search Console để xác nhận Google thấy structured data sau khi render — chi tiết trong checklist Technical SEO.

Phần 9: Web Components và Shadow DOM

Khi render, Google làm phẳng (flatten) shadow DOM và light DOM — nên nội dung trong shadow DOM vẫn có thể được index nếu setup đúng.

html

<script>
  class MyComponent extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: 'open' });
    }
    connectedCallback() {
      const p = document.createElement('p');
      p.innerHTML = 'Nội dung shadow DOM. Và đây là light DOM: <slot></slot>';
      this.shadowRoot.appendChild(p);
    }
  }
  window.customElements.define('my-component', MyComponent);
</script>

<my-component>
  <p>Đây là nội dung light DOM, được chiếu vào shadow DOM qua slot.</p>
</my-component>

Nguyên tắc: đảm bảo nội dung quan trọng hiện diện trong HTML đã render; dùng <slot> để chiếu light DOM; tránh để nội dung chỉ nằm trong shadow DOM mà không có slot.

Phần 10: Lazy Loading hình ảnh

Ảnh tốn nhiều băng thông, nên lazy-loading là chiến lược tốt. Ưu tiên native lazy loading:

html

<img src="product.jpg"
     alt="Áo thun nam cotton trắng"
     loading="lazy"
     width="600" height="400">

Hoặc dùng Intersection Observer khi cần kiểm soát chi tiết:

javascript

const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      imageObserver.unobserve(img);
    }
  });
});

document.querySelectorAll('img[data-src]').forEach(img => imageObserver.observe(img));

NÊN: lazy-load ảnh dưới màn hình đầu; luôn có width/height; có alt text; có placeholder. KHÔNG NÊN: lazy-load ảnh above-the-fold (đặc biệt là ảnh LCP, vì sẽ làm chậm Core Web Vitals); chặn nội dung cho tới khi ảnh tải xong.

Phần 11: Debug JavaScript SEO

Công cụ chính: URL Inspection Tool (Search Console → dán URL → "View tested page" → xem screenshot và HTML đã render — cách chắc chắn nhất để biết Google thấy gì); Rich Results Test (kiểm tra structured data + HTML render); Chrome DevTools (tắt JS rồi reload để xem trang trông thế nào khi không có JS); và Lighthouse.

Lỗi

Nguyên nhân

Cách sửa

Nội dung không được index

CSR không có SSR

Triển khai SSR/SSG

Soft 404

Trang lỗi trả status 200

Trả 404 thật

Title trùng lặp

Title không cập nhật theo route

Dùng react-helmet / next/head

Thiếu canonical

Quên khai báo

Thêm canonical tag

Link không được crawl

Dùng <button> thay vì <a>

Dùng <a href> đúng chuẩn

Render chậm

Bundle quá lớn

Code splitting

Phần 12: Best practice theo framework

javascript

// Next.js — SSG cho nội dung tĩnh
export async function getStaticProps() {
  const data = await fetchData();
  return { props: { data }, revalidate: 60 };
}

// SSR cho nội dung động + xử lý 404 đúng cách
export async function getServerSideProps({ params }) {
  const product = await fetchProduct(params.id);
  if (!product) return { notFound: true };
  return { props: { product } };
}

javascript

// Nuxt 3 — SSR mặc định
const { data } = await useFetch('/api/products');
useHead({
  title: 'Products | Shop XYZ',
  meta: [{ name: 'description', content: 'Browse our products' }]
});

javascript

// SvelteKit — SSR mặc định, trả 404 chuẩn
export async function load({ params }) {
  const product = await fetchProduct(params.id);
  if (!product) error(404, 'Not found');
  return { product };
}

Với Angular, dùng Angular Universal để bật SSR.

Phần 13: Checklist JavaScript SEO

Giai đoạn

Cần kiểm tra

Trước khi code

Chọn framework hỗ trợ SSR/SSG; xác định chiến lược render; routing dùng History API; lên kế hoạch fingerprinting

Khi phát triển

Title/description độc nhất mỗi trang; canonical đúng; HTTP status đúng; structured data; dùng <a href> cho điều hướng; tránh fragment #; native lazy-load ảnh; có alt text

Kiểm thử

URL Inspection Tool; Rich Results Test; Lighthouse; test khi tắt JS; crawl thử bằng Screaming Frog

Triển khai

Thiết lập Search Console; submit sitemap; theo dõi crawl stats và lỗi

Kết luận

JavaScript SEO không phải bất khả thi — chỉ cần làm đúng cách. Với các framework hiện đại (Next.js, Nuxt, SvelteKit), việc xây SPA thân thiện SEO đã dễ hơn rất nhiều. Năm thông điệp cốt lõi: (1) SSR/SSG luôn tốt hơn CSR thuần cho SEO; (2) dùng History API, tránh fragment cho routing; (3) trả đúng status code — xử lý 404 tử tế; (4) test bằng URL Inspection để biết Google thực sự thấy gì; và (5) dùng content fingerprinting vì Google cache rất tích cực.

JavaScript SEO là nền tảng giúp website hiện đại vừa có trải nghiệm tốt vừa thân thiện với Google Search. Nếu bạn muốn xây dựng website React, Vue hoặc Next.js chuẩn SEO, 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 về JavaScript SEO. Code sample và hướng dẫn theo framework thuộc về Tấn Phát Digital.

Câu hỏi thường gặp

JavaScript SEO là gì?

JavaScript SEO là quá trình tối ưu website dùng JavaScript để công cụ tìm kiếm có thể crawl, render và index nội dung chính xác. Nó đặc biệt quan trọng với các site dùng React, Vue hoặc SPA vì nội dung thường được tạo ra sau khi trình duyệt chạy JavaScript.

Vì sao website React hoặc Vue thường khó SEO hơn website HTML tĩnh?

Với React hoặc Vue, nhiều nội dung chỉ xuất hiện sau khi JavaScript được thực thi. Nếu bot không render đầy đủ hoặc render chậm, nội dung quan trọng có thể không được index. Điều này ảnh hưởng đến khả năng xếp hạng, hiển thị title, meta và nội dung trang.

Google có đọc được nội dung tạo bằng JavaScript không?

Có, Google có thể render JavaScript, nhưng không phải lúc nào cũng xử lý ngay lập tức và đầy đủ như trình duyệt người dùng. Vì vậy, nếu nội dung quan trọng phụ thuộc hoàn toàn vào JavaScript, website vẫn có nguy cơ bị index chậm hoặc thiếu dữ liệu.

SSR, CSR và SSG khác nhau thế nào trong SEO?

CSR render nội dung chủ yếu ở phía client nên SEO thường khó hơn. SSR tạo HTML sẵn từ server nên bot dễ đọc hơn. SSG build HTML tĩnh trước khi deploy, rất tốt cho tốc độ và index. Với SEO, SSR và SSG thường an toàn hơn CSR.

React nên dùng cách nào để thân thiện SEO hơn?

Với React, nên ưu tiên framework hỗ trợ SSR hoặc SSG như Next.js nếu website cần SEO mạnh. Ngoài ra cần tối ưu title, meta description, heading, internal link, canonical và đảm bảo nội dung chính xuất hiện trong HTML render đầu tiên.

Vue có thể SEO tốt không?

Có, Vue vẫn SEO tốt nếu triển khai đúng. Thông thường nên dùng Nuxt để hỗ trợ SSR hoặc SSG thay vì chỉ dùng SPA thuần. Quan trọng là bot có thể thấy nội dung, meta tags, URL chuẩn và cấu trúc trang rõ ràng ngay từ lần tải đầu.

Làm sao kiểm tra Google có thấy nội dung JavaScript trên website hay không?

Bạn có thể dùng URL Inspection trong Google Search Console để xem Google render trang như thế nào. Ngoài ra, kiểm tra source HTML, rendered HTML và dùng công cụ kiểm tra crawl để xác định nội dung, title, meta, link và structured data có xuất hiện đúng không.

Những lỗi JavaScript SEO phổ biến trên website React và Vue là gì?

Các lỗi thường gặp gồm: nội dung chính chỉ tải sau API chậm, title và meta thay đổi không đúng, route không tạo URL chuẩn, thiếu liên kết HTML crawl được, chặn file JS/CSS trong robots.txt, và dùng hash URL khiến bot khó hiểu cấu trúc nội dung.

Website SPA có cần prerender không?

Prerender hữu ích khi website dùng SPA nhưng vẫn cần bot thấy nội dung tĩnh ngay lập tức. Nó tạo sẵn HTML cho các trang quan trọng như trang dịch vụ, danh mục, bài viết. Tuy nhiên, nếu dùng SSR hoặc SSG tốt thì thường không cần thêm prerender.

Ngoài render, yếu tố nào còn ảnh hưởng SEO của website JavaScript?

Tốc độ tải trang, Core Web Vitals, cấu trúc heading, internal link, sitemap, canonical, dữ liệu có cấu trúc và khả năng crawl vẫn rất quan trọng. Dù render tốt, website vẫn khó lên top nếu nội dung yếu, điều hướng kém hoặc tải tài nguyên quá nặng.

Bài cùng chuyên mục

Bài trụ cột của chủ đề

Hình ảnh đại diện của bài viết: Nghiên Cứu Từ Khóa SEO 2026: Tìm Từ Khóa "Hái Ra Tiền"
Bài trụ cột

Nghiên Cứu Từ Khóa SEO 2026: Tìm Từ Khóa "Hái Ra Tiền"

Nghiên cứu từ khóa SEO cho người mới 2026: phân biệt từ khóa thông tin vs "hái ra tiền", chọn theo ý định tìm kiếm, công cụ miễn phí và lập bản đồ từ khóa.

Bài mới nhất cùng chuyên mục

Zalo
Facebook
Tấn Phát Digital
Zalo
Facebook