Skip to main content

กำลังโหลด...

Southern Whale
รับ SEO Audit ฟรี
Technical SEO 22 นาทีอ่าน

JavaScript SEO ปี 2026 — React, Vue, Angular ทำ SEO ยังไงไม่ให้ Google มองไม่เห็น | Southern Whale

คู่มือ JavaScript SEO ปี 2026 ครบทุก framework — React, Vue, Angular, Svelte, Astro — เข้าใจ Googlebot Render Process, 2 Wave Crawling, Render Budget, CSR vs SSR vs SSG vs ISR + Common SPA Pitfalls + Schema in SPA + ตัวอย่างจริงเพิ่ม traffic 210%

JavaScript SEO diagram — Googlebot render process กับ React/Vue/Angular

บทนำ: ทำไมคุณยังเสีย Traffic เพราะ JavaScript ในปี 2026

ถ้าคุณเป็นนักพัฒนาเว็บหรือเจ้าของธุรกิจที่เลือกใช้ React, Vue หรือ Angular เพราะ “เร็ว สวย ทันสมัย” แล้ววันหนึ่งเปิด Google Search Console มาเจอว่าหน้าเว็บของคุณไม่ติดอันดับเลย ทั้งที่ content ดีมาก โครงสร้างเรียบร้อย — ปัญหาส่วนใหญ่ไม่ได้อยู่ที่ content แต่อยู่ที่ Googlebot มองไม่เห็นเว็บของคุณ

ในปี 2026 แม้ Googlebot จะ render JavaScript ได้ดีขึ้นมากเมื่อเทียบกับ 5 ปีก่อน แต่ปัญหา JavaScript SEO ยังคงเป็นสาเหตุอันดับต้นๆ ที่ทำให้เว็บ SPA (Single Page Application) เสีย traffic แบบที่เจ้าของเว็บไม่รู้ตัว เพราะ Googlebot ยังต้องเสีย budget ในการ render JavaScript ซึ่งแตกต่างจาก HTML ธรรมดาที่ crawl ปุ๊บเข้าใจปั๊บ

บทความนี้คุณจะได้เรียนรู้:

  • Googlebot Render Process ทำงานจริงๆ อย่างไร ตั้งแต่ crawl → render → index
  • 2 Wave Crawling ที่หลายคนยังเข้าใจผิด
  • Render Budget ที่จำกัดและทำไมมันสำคัญ
  • เปรียบเทียบ CSR vs SSR vs SSG vs ISR ว่าควรเลือกอันไหน
  • Framework comparison — React, Vue, Angular, Svelte, Astro framework ไหนทำ SEO ได้ดีกว่ากัน
  • Common SPA Pitfalls ที่ทำให้คุณเสีย ranking โดยไม่รู้ตัว
  • กรณีศึกษาจริง: ลูกค้า Southern Whale ที่ย้ายจาก React CSR ไป Astro SSG แล้ว organic traffic เพิ่มขึ้น +210% ภายใน 4 เดือน

ถ้าคุณกำลังตัดสินใจเลือก framework หรือกำลังเจอปัญหา SEO บน SPA ที่มีอยู่แล้ว บทความนี้จะช่วยให้คุณตัดสินใจถูกต้องและแก้ปัญหาได้ตรงจุด ไม่ต้องลองผิดลองถูก


1. ทำไม JavaScript SEO ยังยากในปี 2026

หลายคนคิดว่า “Google ก็ render JavaScript ได้แล้วนี่ ไม่น่าจะมีปัญหา” — ความเข้าใจนี้ถูกเพียงครึ่งเดียว เพราะแม้ Googlebot จะใช้ Chromium เวอร์ชันล่าสุดในการ render (ปัจจุบันคือ Chrome 130+) แต่ความจริงที่หลายคนไม่รู้คือ:

1.1 ปัญหาที่ยังคงอยู่ในปี 2026

ปัญหาที่ 1: Render Queue — Googlebot ไม่ได้ render ทุกหน้าทันที หน้าที่ต้อง render JS จะถูกใส่ไว้ใน queue และอาจรอเป็นชั่วโมง วัน หรือสัปดาห์ ก่อนจะถูก render จริง ในขณะที่ HTML ธรรมดาถูก index ได้ทันที

ปัญหาที่ 2: Render Budget จำกัด — Google ให้เวลา render แต่ละหน้าประมาณ 5-10 วินาที ถ้า JavaScript ของคุณโหลดช้ากว่านั้น content บางส่วนอาจหายไป

ปัญหาที่ 3: Search Engine อื่นๆ ยัง render JS ไม่เก่ง — Bing เริ่ม render JS ได้ดีขึ้น แต่ DuckDuckGo, Yandex, Baidu ยังคงมีปัญหา และ AI crawler บางตัว (GPTBot, ClaudeBot) อาจ render JS ไม่ครบ

ปัญหาที่ 4: Social Media Crawler — Facebook, Twitter/X, LinkedIn crawler ส่วนใหญ่ไม่ render JavaScript เลย ทำให้ Open Graph และ Twitter Card ของคุณไม่ทำงาน

ปัญหาที่ 5: Hydration Bugs — แม้คุณจะใช้ SSR แต่ถ้า hydration ผิดพลาด เนื้อหาที่ user เห็นกับที่ Googlebot เห็นอาจไม่ตรงกัน

1.2 สถิติที่น่าตกใจในปี 2026

จากการสำรวจของ Ahrefs ในไตรมาส 1 ปี 2026 พบว่า:

  • เว็บ SPA ที่ใช้ CSR (Client-Side Rendering) มี organic traffic ต่ำกว่า เว็บ SSR เฉลี่ย 53%
  • 47% ของ React project ที่ใช้ CSR มีปัญหา indexing บางหน้า
  • เว็บที่ migrate จาก CSR → SSR/SSG มี traffic เพิ่มขึ้นเฉลี่ย 120-300%
  • 35% ของ JavaScript site มี LCP (Largest Contentful Paint) เกิน 4 วินาที ซึ่งเกินเกณฑ์ Core Web Vitals

ก่อนอ่านต่อ ถ้าคุณอยากเข้าใจเรื่อง Core Web Vitals ที่เกี่ยวข้องกับ JavaScript performance โดยตรง อ่านบทความ Core Web Vitals Guide 2026 ของเราที่อธิบายเรื่อง INP, LCP, CLS แบบลึก


2. Googlebot Render Process: Crawl → Render → Index

ก่อนจะแก้ปัญหา คุณต้องเข้าใจก่อนว่า Googlebot ทำงานยังไงเมื่อเจอเว็บที่ใช้ JavaScript

2.1 ขั้นตอนทั้งหมดของ Googlebot

1. URL Discovery (เจอ URL ใหม่)

2. Crawl Queue (รอ crawl)

3. Crawl (ดาวน์โหลด HTML ดิบ)

4. Processing (parse HTML, หา links, robots.txt check)

5. Render Queue (สำหรับหน้าที่ต้อง JS) ← จุดสำคัญ!

6. Renderer (Chromium รัน JavaScript)

7. Indexing (เก็บลง index)

ขั้นตอนสำคัญที่หลายคนพลาดคือ ขั้นที่ 5: Render Queue เพราะหน้าเว็บที่ต้อง render JS จะไม่ถูก index ทันที แต่ต้องรอใน queue ก่อน

2.2 2 Wave Crawling — สิ่งที่ Google เคยใช้และยังคงสำคัญ

ปี 2018 Google เคยพูดถึงแนวคิด “2 Wave Indexing” ซึ่งหมายความว่า:

Wave 1: HTML Indexing — Googlebot index HTML ดิบที่ได้จาก server ทันที (Server Response) — content ที่อยู่ในนี้จะถูก index ก่อน

Wave 2: Render Indexing — หน้าที่ต้อง JS จะถูก render ภายหลัง (อาจช้ากว่า Wave 1 หลายวัน) แล้ว content ที่ render ออกมาจะถูก index เพิ่ม

แม้ปัจจุบัน Google บอกว่า render queue เร็วขึ้นแล้ว (ประมาณไม่กี่ชั่วโมงถึงไม่กี่วัน) แต่แนวคิดนี้ยังคงสำคัญ เพราะหมายความว่า:

  • ถ้า content สำคัญของคุณอยู่ใน HTML ดิบ → index เร็ว
  • ถ้า content สำคัญต้อง render JS → index ช้ากว่า

นี่คือเหตุผลว่าทำไม SSR/SSG จึงดีกว่า CSR เพราะ content ทั้งหมดอยู่ใน HTML ดิบที่ Wave 1 เข้าถึงได้

2.3 ตัวอย่าง: HTML ที่ Googlebot เห็น

กรณี React CSR (แย่):

<!DOCTYPE html>
<html>
<head>
  <title>โหลด...</title>
</head>
<body>
  <div id="root"></div>
  <script src="/bundle.js"></script>
</body>
</html>

Googlebot crawl ครั้งแรกจะเห็นแค่นี้ — ไม่มี content เลย ต้องรอ Wave 2 ถึงจะเห็น content จริง

กรณี Next.js SSR (ดี):

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript SEO ปี 2026 | Southern Whale</title>
  <meta name="description" content="คู่มือ JavaScript SEO ครบทุก framework...">
</head>
<body>
  <div id="__next">
    <header>...</header>
    <main>
      <h1>JavaScript SEO ปี 2026</h1>
      <article>เนื้อหาเต็มที่ Googlebot เห็นได้ทันที...</article>
    </main>
    <footer>...</footer>
  </div>
  <script src="/_next/static/chunks/main.js"></script>
</body>
</html>

Googlebot crawl ปุ๊บเห็น content ครบทันที — index เร็ว ranking ดี


3. Render Budget: Google ใช้เวลา render ประมาณ 5-10 วินาที

นี่คือเรื่องที่หลายคนไม่รู้ — Google ไม่ได้รอ JavaScript ของคุณทำงานเสร็จไม่จำกัด แต่มี budget ที่จำกัด

3.1 Render Budget คืออะไร

Render Budget หมายถึงจำนวนเวลาและทรัพยากร (CPU, memory, bandwidth) ที่ Googlebot ยอมเสียให้กับการ render หน้าเว็บของคุณ ปัจจุบันอยู่ที่ประมาณ:

  • เวลา: 5-10 วินาทีต่อหน้า (อาจน้อยกว่านี้ในเว็บที่ Google คิดว่า “ไม่สำคัญ”)
  • JavaScript size: ไม่มีข้อจำกัดเด็ดขาด แต่ bundle ยิ่งใหญ่ยิ่งใช้เวลา parse นาน
  • Network requests: Googlebot อาจไม่ดาวน์โหลด resource ทั้งหมด โดยเฉพาะ third-party scripts

3.2 ผลกระทบของ Render Budget

ถ้า JavaScript ของคุณ:

  1. โหลดช้า (เกิน 5 วินาที) — Googlebot อาจ render ไม่ทัน content
  2. มี API call ที่ช้า — content ที่ต้อง fetch อาจไม่ถูก index
  3. มี dependency ซับซ้อน — บาง component อาจไม่ render เลย
  4. block by 3rd-party script — เช่น analytics, ads, chat widget ทำให้ render ช้า

3.3 ตัวอย่าง: API Call ที่ Googlebot อาจพลาด

// useFetchPosts.js — แบบที่อันตราย
import { useState, useEffect } from 'react';

export function useFetchPosts() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // ถ้า API ช้าเกิน 5 วินาที Googlebot อาจไม่เห็น posts
    fetch('/api/posts')
      .then(res => res.json())
      .then(data => {
        setPosts(data);
        setLoading(false);
      });
  }, []);

  return { posts, loading };
}

วิธีแก้ที่ปลอดภัย — ใช้ SSR/SSG ดึงข้อมูลก่อนส่ง HTML:

// Next.js - getServerSideProps
export async function getServerSideProps() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();
  
  return {
    props: { posts }, // posts จะอยู่ใน HTML ดิบที่ Googlebot เห็นได้ทันที
  };
}

export default function BlogPage({ posts }) {
  return (
    <main>
      <h1>บทความล่าสุด</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <a href={`/blog/${post.slug}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </main>
  );
}

3.4 เคล็ดลับลด Render Cost

  1. Code splitting — โหลดเฉพาะ JS ที่ใช้
  2. Tree shaking — กำจัด dead code
  3. Lazy load images — แต่ใช้ native loading="lazy" ไม่ใช่ JavaScript
  4. Preload critical resources<link rel="preload">
  5. Defer non-critical scripts<script defer> หรือ <script async>
  6. Cache aggressively — ใช้ HTTP cache headers + CDN

ถ้าคุณยังไม่ได้ใช้ CDN ที่ดี เราแนะนำให้อ่าน Cloudflare Complete Guide 2026 เพื่อ setup CDN ที่ช่วยลด render cost ได้


4. 4 Rendering Strategy: CSR vs SSR vs SSG vs ISR

นี่คือหัวใจของ JavaScript SEO — การเลือก rendering strategy ที่ถูกต้องตามประเภทเว็บของคุณ

4.1 ตารางเปรียบเทียบ Rendering Strategy

StrategySEOPerformanceBuild TimeServer CostUpdate Frequencyเหมาะกับ
CSR (Client-Side Rendering)แย่แย่ตอนแรกเร็วต่ำReal-timeApp ที่ต้อง login
SSR (Server-Side Rendering)ดีมากปานกลางทันทีสูงReal-timeE-commerce, dashboard
SSG (Static Site Generation)ดีที่สุดดีที่สุดนานต่ำสุดตอน buildBlog, landing, marketing
ISR (Incremental Static Regeneration)ดีมากดีปานกลางปานกลางตามช่วงเวลาข่าว, ราคาสินค้า

4.2 CSR (Client-Side Rendering) — Worst สำหรับ SEO

ทำงานยังไง: Server ส่ง HTML ว่างเปล่ามาให้ browser แล้ว JavaScript ใน browser render content ทั้งหมด

ข้อดี:

  • พัฒนาง่าย
  • เร็วหลัง initial load
  • server cost ต่ำ

ข้อเสียสำหรับ SEO:

  • Googlebot ต้อง render JS ก่อนถึงจะเห็น content (Wave 2)
  • Social crawler มองไม่เห็นเลย
  • Time to First Paint แย่
  • Render budget เสียมาก

ตัวอย่าง CSR (React + Vite):

// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>My SPA</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/main.jsx"></script>
</body>
</html>

Googlebot crawl ครั้งแรก → ไม่เห็น content เลย

4.3 SSR (Server-Side Rendering) — Best for Dynamic Content

ทำงานยังไง: ทุกครั้งที่มี request server จะ render React/Vue/Angular ออกมาเป็น HTML แล้วส่งให้ browser

ข้อดีสำหรับ SEO:

  • Googlebot เห็น content เต็มทันที (Wave 1)
  • Social crawler ทำงานได้
  • TTFB ปานกลาง
  • รองรับ dynamic content

ข้อเสีย:

  • Server cost สูง
  • ต้องใช้ Node.js server
  • การ scale ยากกว่า static

ตัวอย่าง Next.js SSR:

// pages/products/[id].js
export async function getServerSideProps({ params }) {
  const res = await fetch(`https://api.example.com/products/${params.id}`);
  const product = await res.json();
  
  if (!product) {
    return { notFound: true };
  }
  
  return { props: { product } };
}

export default function ProductPage({ product }) {
  return (
    <>
      <Head>
        <title>{product.name} | My Shop</title>
        <meta name="description" content={product.description} />
      </Head>
      <main>
        <h1>{product.name}</h1>
        <p>{product.description}</p>
        <p>ราคา: {product.price} บาท</p>
      </main>
    </>
  );
}

ตัวอย่าง Nuxt 3 SSR:

<!-- pages/products/[id].vue -->
<script setup>
const route = useRoute();
const { data: product } = await useFetch(`/api/products/${route.params.id}`);

useSeoMeta({
  title: `${product.value.name} | My Shop`,
  description: product.value.description,
});
</script>

<template>
  <main>
    <h1>{{ product.name }}</h1>
    <p>{{ product.description }}</p>
    <p>ราคา: {{ product.price }} บาท</p>
  </main>
</template>

4.4 SSG (Static Site Generation) — Best for SEO

ทำงานยังไง: ตอน build time generate HTML ทุกหน้าไว้ล่วงหน้า แล้ว serve เป็น static file

ข้อดีสำหรับ SEO:

  • Googlebot เห็น content เต็มทันที
  • TTFB เร็วที่สุด (ส่ง static file)
  • LCP ดีที่สุด
  • Cost ต่ำสุด (host บน CDN ได้)
  • รองรับ AI crawler ทุกตัว

ข้อเสีย:

  • Build time นาน (ถ้ามีหน้าเยอะ)
  • ไม่เหมาะ real-time data

ตัวอย่าง Astro SSG (ที่เว็บ Southern Whale ใช้):

---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
import Layout from '../../layouts/BlogPost.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await post.render();
---

<Layout 
  title={post.data.title}
  description={post.data.description}
  image={post.data.image}
>
  <article>
    <h1>{post.data.title}</h1>
    <Content />
  </article>
</Layout>

อยากเรียนรู้เรื่อง Astro แบบลึก อ่าน Astro Framework Guide 2026 ของเรา

4.5 ISR (Incremental Static Regeneration) — Best of Both Worlds

ทำงานยังไง: Generate static ตอน build แต่สามารถ re-generate บางหน้าตามช่วงเวลาที่กำหนด

ตัวอย่าง Next.js ISR:

// pages/blog/[slug].js
export async function getStaticProps({ params }) {
  const res = await fetch(`https://api.example.com/posts/${params.slug}`);
  const post = await res.json();
  
  return {
    props: { post },
    revalidate: 3600, // re-generate ทุก 1 ชั่วโมง
  };
}

export async function getStaticPaths() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();
  
  const paths = posts.map(post => ({
    params: { slug: post.slug },
  }));
  
  return { paths, fallback: 'blocking' };
}

5. เปรียบเทียบ Framework: React, Vue, Angular, Svelte, Astro

แต่ละ framework มีแนวทาง SEO ที่แตกต่างกัน เลือกผิดอาจเสียหายระยะยาว

5.1 ตารางเปรียบเทียบ Framework SEO

FrameworkDefaultSSR SolutionSSG SupportSEO ScoreHydration Costเหมาะกับ
ReactCSRNext.jsNext.js / Gatsbyดี (with Next)สูงApp ทุกประเภท
VueCSRNuxt 3Nuxt 3 / VitePressดี (with Nuxt)ปานกลางApp, blog
AngularCSRAngular UniversalScullyปานกลางสูงที่สุดEnterprise app
SvelteCSRSvelteKitSvelteKitดีมากต่ำApp ขนาดเล็ก-กลาง
AstroSSGBuilt-inBuilt-in (default)ดีที่สุดต่ำที่สุด (Islands)Blog, marketing, content site

5.2 React + Next.js — เลือกที่นิยมที่สุด

ข้อดี:

  • Ecosystem ใหญ่ที่สุด
  • SSR/SSG/ISR ครบ
  • Image optimization (next/image)
  • Built-in routing
  • Server Components (Next.js 13+) ลด hydration cost

ข้อเสีย:

  • Bundle size ใหญ่
  • Hydration cost สูง
  • Vendor lock-in กับ Vercel

ตัวอย่าง SEO Setup ที่ดี:

// app/blog/[slug]/page.js (Next.js 14 App Router)
import { notFound } from 'next/navigation';

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  if (!post) return {};
  
  return {
    title: post.title,
    description: post.description,
    openGraph: {
      title: post.title,
      description: post.description,
      images: [post.image],
      type: 'article',
      publishedTime: post.date,
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.description,
      images: [post.image],
    },
    alternates: {
      canonical: `https://example.com/blog/${params.slug}`,
    },
  };
}

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug);
  if (!post) notFound();
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

5.3 Vue + Nuxt 3 — Performance ดี ใช้ง่าย

ข้อดี:

  • Bundle size เล็กกว่า React
  • API ใช้ง่ายกว่า
  • SSR/SSG ในตัว
  • Auto-import ทำให้โค้ดสะอาด
  • Image optimization ในตัว

ตัวอย่าง:

<!-- pages/blog/[slug].vue -->
<script setup>
const route = useRoute();
const { data: post } = await useAsyncData(
  `post-${route.params.slug}`,
  () => $fetch(`/api/posts/${route.params.slug}`)
);

if (!post.value) {
  throw createError({ statusCode: 404, message: 'Post not found' });
}

useSeoMeta({
  title: `${post.value.title} | My Blog`,
  description: post.value.description,
  ogTitle: post.value.title,
  ogDescription: post.value.description,
  ogImage: post.value.image,
  ogType: 'article',
  twitterCard: 'summary_large_image',
});

useHead({
  link: [
    { rel: 'canonical', href: `https://example.com/blog/${route.params.slug}` },
  ],
});
</script>

<template>
  <article>
    <h1>{{ post.title }}</h1>
    <div v-html="post.content" />
  </article>
</template>

5.4 Angular + Universal — Enterprise Choice

Angular Universal เป็น SSR solution ของ Angular ที่ใช้สำหรับเว็บ Enterprise ขนาดใหญ่

ข้อดี:

  • TypeScript first
  • Enterprise-grade
  • Built-in DI
  • RxJS สำหรับ async

ข้อเสีย:

  • Learning curve สูง
  • Bundle size ใหญ่ที่สุดในกลุ่ม
  • Hydration cost สูงที่สุด
  • Setup ยุ่งกว่า framework อื่น

ตัวอย่าง Angular Universal:

// app.server.module.ts
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [AppModule, ServerModule],
  bootstrap: [AppComponent],
})
export class AppServerModule {}
// blog.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Meta, Title } from '@angular/platform-browser';
import { BlogService } from './blog.service';

@Component({
  selector: 'app-blog-post',
  template: `
    <article *ngIf="post">
      <h1>{{ post.title }}</h1>
      <div [innerHTML]="post.content"></div>
    </article>
  `,
})
export class BlogPostComponent implements OnInit {
  post: any;
  
  constructor(
    private route: ActivatedRoute,
    private blogService: BlogService,
    private title: Title,
    private meta: Meta,
  ) {}
  
  ngOnInit() {
    const slug = this.route.snapshot.paramMap.get('slug');
    this.blogService.getPost(slug).subscribe(post => {
      this.post = post;
      this.title.setTitle(`${post.title} | My Blog`);
      this.meta.updateTag({ name: 'description', content: post.description });
      this.meta.updateTag({ property: 'og:title', content: post.title });
    });
  }
}

5.5 Svelte + SvelteKit — Performance King

ข้อดี:

  • Bundle size เล็กที่สุด
  • Hydration cost ต่ำสุด (รองจาก Astro)
  • Syntax สวยงาม
  • SSR/SSG/CSR ในตัว

ตัวอย่าง:

<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
  export let data;
</script>

<svelte:head>
  <title>{data.post.title} | My Blog</title>
  <meta name="description" content={data.post.description} />
  <meta property="og:title" content={data.post.title} />
</svelte:head>

<article>
  <h1>{data.post.title}</h1>
  {@html data.post.content}
</article>
// src/routes/blog/[slug]/+page.server.js
export async function load({ params }) {
  const res = await fetch(`https://api.example.com/posts/${params.slug}`);
  if (!res.ok) {
    throw error(404, 'Post not found');
  }
  const post = await res.json();
  return { post };
}

5.6 Astro — SEO King ในปี 2026

ข้อดี:

  • SSG by default → SEO ดีที่สุด
  • Zero JavaScript by default (Islands Architecture)
  • รองรับ React/Vue/Svelte/Solid ใน component เดียวกัน
  • Image optimization ใน framework
  • Built-in MDX, RSS, Sitemap
  • ขนาด bundle เล็กที่สุด

ตัวอย่าง:

---
// src/pages/blog/[slug].astro
import Layout from '../../layouts/Blog.astro';
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await post.render();
---

<Layout 
  title={post.data.title}
  description={post.data.description}
  ogImage={post.data.image}
>
  <article>
    <h1>{post.data.title}</h1>
    <time datetime={post.data.date.toISOString()}>
      {post.data.date.toLocaleDateString('th-TH')}
    </time>
    <Content />
  </article>
</Layout>

6. Test JavaScript SEO: เครื่องมือที่ต้องใช้

ก่อนปล่อยเว็บออนไลน์ คุณต้องเทสว่า Googlebot เห็นเว็บคุณตามที่ต้องการจริงหรือไม่

6.1 Google Search Console — URL Inspection Tool

นี่คือเครื่องมือสำคัญที่สุด — แสดงให้คุณเห็นว่า Googlebot เห็นหน้าเว็บคุณยังไง

วิธีใช้:

  1. เปิด Google Search Console
  2. ใส่ URL ที่ต้องการตรวจสอบ
  3. คลิก “Test Live URL”
  4. ดู “Rendered HTML” และ “Screenshot”
  5. ดู “More Info” → “Page Resources” เพื่อเช็คว่า resource อะไรโหลดไม่สำเร็จ

สิ่งที่ต้องเช็ค:

  • ✅ Title และ Meta description อยู่ใน HTML
  • ✅ H1, H2 อยู่ใน HTML
  • ✅ Main content อยู่ใน HTML
  • ✅ Links เป็น <a href> ที่ Googlebot follow ได้
  • ✅ Image มี alt text
  • ✅ ไม่มี error ใน console

6.2 Google Mobile-Friendly Test

ทดสอบว่าเว็บคุณรองรับมือถือและ render ได้สมบูรณ์

https://search.google.com/test/mobile-friendly?url=YOUR_URL

6.3 Screaming Frog SEO Spider with JavaScript Rendering

สำหรับ audit ทั้งเว็บ:

Configuration:

  1. Configuration → Spider → Rendering → JavaScript
  2. ตั้ง User-Agent: Googlebot Smartphone
  3. ตั้ง AJAX Timeout: 5 seconds (จำลอง render budget)
  4. Crawl

สิ่งที่ Screaming Frog บอกได้:

  • หน้าไหนต้อง JS render
  • หน้าไหน content ขาดหายหลัง render
  • Internal links ที่ Googlebot follow ได้
  • Schema markup ที่ render สำเร็จ

6.4 Manual Test ด้วย curl

ดู HTML ดิบที่ server ส่งมา (ไม่ผ่าน JS render):

# ดูเหมือน Googlebot
curl -A "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://yoursite.com/blog/post-slug

ถ้าเห็นแต่ <div id="root"></div> → คุณใช้ CSR และ Googlebot จะมีปัญหา

6.5 Chrome DevTools — Network Throttling

จำลอง slow network เพื่อทดสอบว่า render ทันใน 5 วินาทีไหม:

  1. เปิด DevTools → Network
  2. Throttling → “Slow 3G”
  3. Reload
  4. ดูว่า content ทั้งหมด render ทันใน 5 วินาทีหรือไม่

ถ้าไม่ทัน → Googlebot อาจไม่เห็น content บางส่วน


7. Common SPA Pitfalls — กับดักที่ทำให้คุณเสีย Ranking

ต่อให้คุณใช้ SSR แล้วก็ยังมีกับดักหลายอย่างที่ทำลาย SEO ได้

7.1 Pitfall #1: Empty <div id="root">

นี่คือปัญหาคลาสสิคของ CSR — Googlebot crawl ครั้งแรกเห็นแต่ div ว่าง

ตรวจสอบ: ดู View Source (ไม่ใช่ Inspect Element) ของหน้าเว็บคุณ

<!-- ถ้าเห็นแบบนี้ = ปัญหาแน่นอน -->
<body>
  <div id="root"></div>
  <script src="/bundle.js"></script>
</body>

วิธีแก้: ย้ายไปใช้ Next.js / Nuxt / SvelteKit / Astro

7.2 Pitfall #2: Lazy-loaded Content ที่ใช้ JavaScript

// แย่: Content ที่ลึกในหน้า lazy load ด้วย Intersection Observer
function LazyContent() {
  const [visible, setVisible] = useState(false);
  const ref = useRef();
  
  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) setVisible(true);
    });
    observer.observe(ref.current);
  }, []);
  
  return (
    <div ref={ref}>
      {visible && <BigContent />} {/* Googlebot อาจไม่ scroll ลงมา = ไม่เห็น content */}
    </div>
  );
}

วิธีแก้: ใช้ native lazy loading สำหรับ image และ render content ใน HTML ทันที

<!-- ดี: Native lazy loading -->
<img src="image.jpg" loading="lazy" alt="..." />

<!-- ดี: Content render ทันที CSS ซ่อน -->
<div class="hidden md:block">
  Content ที่แสดงเฉพาะ desktop
</div>

7.3 Pitfall #3: Infinite Scroll without Pagination

Infinite scroll ที่ใช้ JS ล้วน Googlebot จะเห็นแค่หน้าแรก

วิธีแก้: ทำ pagination จริงๆ ใน URL คู่กับ infinite scroll

<!-- ตอน render ครั้งแรก -->
<main>
  <article>Post 1</article>
  <article>Post 2</article>
  <!-- ... 10 posts -->
  
  <nav aria-label="pagination">
    <a href="/blog?page=1" rel="prev">ก่อนหน้า</a>
    <a href="/blog?page=3" rel="next">ถัดไป</a>
  </nav>
</main>

แล้ว enhance ด้วย infinite scroll สำหรับ user experience:

// JS เพิ่ม infinite scroll สำหรับ user
window.addEventListener('scroll', () => {
  if (nearBottom()) {
    loadMorePosts();
  }
});

วิธีนี้ Googlebot follow <a href> ได้ และ user ได้ infinite scroll

7.4 Pitfall #4: Tab Content ที่ Hidden

<!-- แย่: Content ใน tab ที่ไม่ active อาจไม่ถูก index -->
<div class="tabs">
  <button onClick={() => setActive('a')}>Tab A</button>
  <button onClick={() => setActive('b')}>Tab B</button>
  
  {active === 'a' && <div>Content A</div>}
  {active === 'b' && <div>Content B</div>}
</div>

วิธีแก้: Render content ทั้งหมดใน HTML แล้วใช้ CSS ซ่อน

<div class="tabs">
  <div class="tab-content" id="tab-a">Content A</div>
  <div class="tab-content hidden" id="tab-b">Content B</div>
</div>

7.5 Pitfall #5: Hash Fragments

<!-- แย่: # ใน URL ไม่ถูก crawl เป็น URL แยก -->
https://example.com/#/products/123
https://example.com/#/about

วิธีแก้: ใช้ History API หรือ framework routing ที่สร้าง URL จริง

<!-- ดี: URL จริง crawl ได้ -->
https://example.com/products/123
https://example.com/about

8. Soft 404 Problem in SPA

นี่คือปัญหาที่หลายคนไม่รู้ตัว — SPA อาจส่ง HTTP 200 OK แต่แสดงข้อความ “Not Found” ทำให้ Google คิดว่าเป็น Soft 404

8.1 ปัญหา Soft 404 ใน SPA

// แย่: React component แสดง 404 message แต่ server ส่ง 200
function ProductPage({ id }) {
  const { product, loading } = useFetchProduct(id);
  
  if (loading) return <Spinner />;
  if (!product) return <h1>ไม่พบสินค้า</h1>; // server ยังส่ง 200!
  
  return <ProductDetail product={product} />;
}

Google จะเห็น HTTP 200 + “ไม่พบสินค้า” → Soft 404 → ไม่ index → เสียพื้นที่ใน crawl budget

8.2 วิธีแก้ใน Next.js

// pages/products/[id].js
export async function getServerSideProps({ params }) {
  const product = await fetchProduct(params.id);
  
  if (!product) {
    return { notFound: true }; // ส่ง HTTP 404 จริง
  }
  
  return { props: { product } };
}

8.3 วิธีแก้ใน Nuxt 3

<script setup>
const route = useRoute();
const { data: product } = await useFetch(`/api/products/${route.params.id}`);

if (!product.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Product not found',
  });
}
</script>

8.4 วิธีแก้ใน Astro

---
const { id } = Astro.params;
const product = await fetchProduct(id);

if (!product) {
  return Astro.redirect('/404', 404);
}
---

<Layout>
  <ProductDetail product={product} />
</Layout>

8.5 เช็ค Soft 404 ใน GSC

ไปที่ Google Search Console → Coverage → “Soft 404”

ถ้าเจอเยอะ = SPA ของคุณส่ง status code ไม่ถูกต้อง


9. Internal Linking ที่ Crawler เจอ — real <a href>, not onClick

นี่คือกฎทอง — Googlebot follow แค่ <a href> ไม่ follow onClick

9.1 ตัวอย่างที่ผิด (Googlebot ไม่ follow)

// แย่ทุกแบบ - Googlebot ไม่ follow
<button onClick={() => navigate('/about')}>About</button>
<div onClick={() => router.push('/about')}>About</div>
<span onClick={handleClick}>About</span>
<a onClick={(e) => { e.preventDefault(); navigate('/about'); }}>About</a>
<a href="#" onClick={handleClick}>About</a>
<a href="javascript:void(0)" onClick={handleClick}>About</a>

9.2 ตัวอย่างที่ถูก

// ดี - Googlebot follow ได้
<a href="/about">About</a>

// ดี - Next.js Link (render เป็น <a href> จริง)
import Link from 'next/link';
<Link href="/about">About</Link>

// ดี - Nuxt NuxtLink
<NuxtLink to="/about">About</NuxtLink>

// ดี - Astro
<a href="/about">About</a>

9.3 เคล็ดลับ Internal Linking

  1. ใช้ descriptive anchor text — “JavaScript SEO Guide” ดีกว่า “click here”
  2. ลิงก์จากหน้า authority — ลิงก์จาก homepage ไปหน้าสำคัญ
  3. สร้าง topic cluster — รวมหน้าที่เกี่ยวข้องเข้าด้วยกัน
  4. Internal link จากบทความใหม่ไปบทความเก่า — ส่ง link equity
  5. Breadcrumbs — ใช้ navigation ที่มี <a href> จริง

ถ้าคุณต้องการให้ทีม Southern Whale ช่วย audit เรื่อง internal linking ของเว็บคุณ ติดต่อเราได้ที่ หน้า contact


10. Schema Markup ใน SPA — Use JSON-LD, Not Microdata

Schema markup ช่วยให้ Google เข้าใจ content ของคุณดีขึ้น และเป็นเครื่องมือสำคัญสำหรับ Rich Results

10.1 ทำไม JSON-LD ดีกว่า Microdata ใน SPA

รูปแบบข้อดีข้อเสียเหมาะกับ SPA
JSON-LDแยกจาก HTML, ง่ายต่อการ generate dynamicต้อง render ใน HTML ดิบ✅ ใช่
Microdataฝังใน HTMLกระจาย, ทำ dynamic ยาก❌ ไม่เหมาะ
RDFaคล้าย Microdataซับซ้อนกว่า❌ ไม่เหมาะ

Google แนะนำ JSON-LD เป็นทางเลือกแรก

10.2 ตัวอย่าง JSON-LD ในบทความ Blog

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "JavaScript SEO ปี 2026",
  "description": "คู่มือ JavaScript SEO ครบทุก framework",
  "image": [
    "https://example.com/images/blog/javascript-seo-2026.webp"
  ],
  "datePublished": "2026-06-20T08:00:00+07:00",
  "dateModified": "2026-06-20T08:00:00+07:00",
  "author": {
    "@type": "Organization",
    "name": "Southern Whale",
    "url": "https://southernwhale.com"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Southern Whale",
    "logo": {
      "@type": "ImageObject",
      "url": "https://southernwhale.com/logo.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://southernwhale.com/blog/javascript-seo-2026"
  }
}
</script>

10.3 Schema สำหรับ Product (E-commerce SPA)

// Next.js component
export default function ProductSchema({ product }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    description: product.description,
    image: product.images,
    sku: product.sku,
    brand: {
      '@type': 'Brand',
      name: product.brand,
    },
    offers: {
      '@type': 'Offer',
      url: `https://example.com/products/${product.slug}`,
      priceCurrency: 'THB',
      price: product.price,
      availability: product.inStock 
        ? 'https://schema.org/InStock' 
        : 'https://schema.org/OutOfStock',
    },
    aggregateRating: {
      '@type': 'AggregateRating',
      ratingValue: product.rating,
      reviewCount: product.reviewCount,
    },
  };
  
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  );
}

10.4 Schema สำหรับ FAQ

const faqSchema = {
  '@context': 'https://schema.org',
  '@type': 'FAQPage',
  mainEntity: faqs.map(faq => ({
    '@type': 'Question',
    name: faq.question,
    acceptedAnswer: {
      '@type': 'Answer',
      text: faq.answer,
    },
  })),
};

10.5 Schema สำหรับ Organization (ทุกหน้า)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Southern Whale",
  "url": "https://southernwhale.com",
  "logo": "https://southernwhale.com/logo.png",
  "sameAs": [
    "https://www.facebook.com/southernwhale",
    "https://twitter.com/southernwhale",
    "https://www.linkedin.com/company/southernwhale"
  ],
  "contactPoint": {
    "@type": "ContactPoint",
    "telephone": "+66-2-123-4567",
    "contactType": "customer service",
    "areaServed": "TH",
    "availableLanguage": ["Thai", "English"]
  }
}
</script>

10.6 ข้อควรระวัง Schema ใน SPA

  1. ต้อง render schema ใน HTML ดิบ — ไม่ใช่ inject ด้วย JS หลัง page load
  2. เนื้อหาใน schema ต้องตรงกับเนื้อหาในหน้า — ไม่งั้นเป็น manual penalty
  3. Test ด้วย Rich Results Testsearch.google.com/test/rich-results
  4. อย่าใช้ markup สำหรับ content ที่ user มองไม่เห็น

11. Performance Impact: Hydration Cost

Hydration คือกระบวนการที่ JavaScript “เข้ามาควบคุม” HTML ที่ render มาจาก server — ฟังดูดี แต่มีต้นทุนที่หลายคนไม่รู้

11.1 Hydration คืออะไร

1. Server render HTML (เร็ว)
2. Browser แสดง HTML
3. JavaScript bundle download
4. JavaScript parse + compile
5. React/Vue "hydrate" HTML (เชื่อม event handler)
6. Page interactive (TTI)

ขั้นตอน 3-5 คือ “hydration cost” — ระหว่างนั้น user เห็น UI แต่กดอะไรไม่ได้

11.2 ผลกระทบต่อ Core Web Vitals

INP (Interaction to Next Paint):

  • Hydration ที่ช้า → INP สูง → คะแนนแย่
  • React 18 ใช้ “Selective Hydration” ช่วยได้บ้าง
  • React Server Components ลดได้มาก

LCP:

  • ถ้า hydration block main thread → LCP element อาจ render ช้า

FID/INP:

  • ก่อน hydrate เสร็จ → user click อะไรก็ไม่ตอบสนอง

11.3 ขนาด Bundle ที่แต่ละ Framework สร้าง

FrameworkBundle Size (gzip)Hydration Time (avg)
Astro (Islands)0 KB (default)~0ms
Svelte3-5 KB~50ms
Vue 330-40 KB~150ms
React 1840-50 KB~200ms
Angular100-150 KB~400ms

11.4 วิธีลด Hydration Cost

1. Use Server Components (Next.js / React 19)

// app/blog/page.js — Server Component (ไม่ต้อง hydrate!)
export default async function BlogPage() {
  const posts = await fetchPosts();
  
  return (
    <div>
      <h1>Blog</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
          <InteractiveButton postId={post.id} /> {/* แค่ส่วนนี้ที่ hydrate */}
        </article>
      ))}
    </div>
  );
}

2. Use Islands Architecture (Astro)

---
import StaticHeader from './Header.astro';
import InteractiveSearch from './Search.jsx'; // เฉพาะ component นี้ hydrate
---

<StaticHeader />
<main>
  <h1>Welcome</h1>
  <InteractiveSearch client:load />
  <p>Static content...</p>
</main>

3. Code Splitting + Dynamic Import

// แทนที่จะ import ทั้งหมดทันที
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function Page() {
  return (
    <div>
      <main>Content...</main>
      <Suspense fallback={<Spinner />}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}

4. Defer Non-critical JavaScript

<!-- Critical -->
<script src="/main.js"></script>

<!-- Non-critical (analytics, chat widget) -->
<script src="/analytics.js" defer></script>
<script src="/chat.js" async></script>

12. ตัวอย่างจริง: SW Migrated Client จาก React CSR → Astro SSG (+210%)

นี่คือกรณีศึกษาจริงของลูกค้า Southern Whale ที่เราช่วย migrate และผลลัพธ์น่าทึ่ง

12.1 Background

Client: เว็บไซต์ Marketing สาย B2B SaaS ในประเทศไทย Stack เดิม: React 17 + Create React App + Client-Side Rendering ปัญหา:

  • หน้าเพจ 47 หน้า แต่ Google index แค่ 12 หน้า
  • LCP เฉลี่ย 5.2 วินาที
  • INP เฉลี่ย 480ms
  • Bounce rate 72%
  • Organic traffic 2,300/เดือน

12.2 ปัญหาที่เราเจอ

  1. HTML ดิบเป็น <div id="root"></div> — Googlebot เห็นแต่ skeleton
  2. Title และ Meta description ใช้ react-helmet — บางหน้า render ไม่ทัน → Googlebot ได้ default title
  3. Internal links ใช้ React Router useNavigate แทน <Link> — Googlebot follow ไม่ได้
  4. Schema markup inject ผ่าน useEffect — บางครั้ง render ไม่ทัน
  5. Image lazy load ด้วย JS — Googlebot ไม่เห็น image
  6. Bundle size 1.2MB — slow LCP
  7. API calls ใน useEffect ทำให้ content แสดงช้า — Googlebot timeout

12.3 Migration Plan

Phase 1: Setup Astro Project (สัปดาห์ 1)

npm create astro@latest my-site -- --template minimal --typescript strict
cd my-site
npm install @astrojs/sitemap @astrojs/rss

Phase 2: Content Migration (สัปดาห์ 2-3)

  • Convert React components → Astro components
  • Static content → Astro .astro files
  • Interactive components → Islands (React หรือ Svelte)

Phase 3: SEO Setup (สัปดาห์ 4)

---
// src/layouts/BaseLayout.astro
const { 
  title, 
  description, 
  canonical = Astro.url.href,
  ogImage = '/og-default.png',
} = Astro.props;
---

<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  
  <title>{title}</title>
  <meta name="description" content={description} />
  <link rel="canonical" href={canonical} />
  
  <!-- Open Graph -->
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />
  <meta property="og:image" content={ogImage} />
  <meta property="og:type" content="website" />
  
  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content={title} />
  <meta name="twitter:description" content={description} />
  <meta name="twitter:image" content={ogImage} />
  
  <!-- Sitemap -->
  <link rel="sitemap" href="/sitemap-index.xml" />
</head>
<body>
  <slot />
</body>
</html>

Phase 4: 301 Redirects (สัปดาห์ 5)

// astro.config.mjs
export default defineConfig({
  redirects: {
    '/old-url-1': '/new-url-1',
    '/old-url-2': '/new-url-2',
    // ... ทุก URL เดิมที่เปลี่ยน
  },
});

Phase 5: Launch (สัปดาห์ 6)

12.4 ผลลัพธ์หลัง 4 เดือน

Metricก่อนหลัง 4 เดือนเปลี่ยนแปลง
Pages indexed12 / 4745 / 47+275%
Organic traffic2,300/เดือน7,150/เดือน+210%
LCP5.2s1.4s-73%
INP480ms120ms-75%
Bounce rate72%38%-47%
Conversion rate1.2%3.8%+217%
Bundle size1.2MB45KB-96%

12.5 บทเรียนที่ได้

  1. Astro SSG เหมาะกับเว็บ marketing/blog ที่สุด — ไม่ต้องใช้ JS framework หนัก
  2. Migration ต้องวางแผน 301 redirect ให้ดี — ไม่งั้นเสีย ranking
  3. ใช้ Islands Architecture สำหรับ interactive component — ไม่ต้องทิ้ง React ทั้งหมด
  4. Schema markup ใน SSG = render ทันที = Google เห็นเสมอ
  5. Performance ดี → User experience ดี → Conversion เพิ่ม

ถ้าคุณสนใจให้เราช่วย migrate เว็บคุณบ้าง ดูบริการของเราที่ SEO Services หรือติดต่อเราที่ หน้า contact


13. 5 ข้อผิดพลาดที่พบบ่อยใน JavaScript SEO

ตลอด 6 ปีที่ Southern Whale audit เว็บ SPA มามากมาย เราพบ 5 ข้อผิดพลาดเหล่านี้บ่อยที่สุด

13.1 ข้อผิดพลาดที่ 1: คิดว่า Google render JS ได้ดีพอแล้ว

หลายคน trust Google มากเกินไป — “Google บอกว่า render JS ได้ ก็คงไม่มีปัญหา”

ความจริง:

  • Google render ได้แต่ใช้ resource มาก
  • หน้าที่ไม่สำคัญ Google อาจไม่ render
  • Render budget จำกัด
  • AI crawlers อื่นๆ ไม่ render

วิธีแก้: ใช้ SSR/SSG เสมอถ้า SEO สำคัญกับคุณ

13.2 ข้อผิดพลาดที่ 2: ลืม Update Meta Tags เมื่อ Route Change

ใน SPA route เปลี่ยนแต่ meta tags ไม่เปลี่ยน

// แย่: Meta tags ไม่ update เมื่อเปลี่ยนหน้า
function ProductPage({ id }) {
  // title, description ของหน้าก่อนหน้ายังอยู่
}

วิธีแก้ใน React:

import { Helmet } from 'react-helmet-async';

function ProductPage({ product }) {
  return (
    <>
      <Helmet>
        <title>{product.name} | My Shop</title>
        <meta name="description" content={product.description} />
      </Helmet>
      {/* ... */}
    </>
  );
}

วิธีแก้ใน Vue 3:

<script setup>
useHead({
  title: `${product.name} | My Shop`,
  meta: [
    { name: 'description', content: product.description },
  ],
});
</script>

13.3 ข้อผิดพลาดที่ 3: ใช้ display: none ซ่อน Content แต่ render ใน HTML

<!-- แย่: Content ที่ user มองไม่เห็น แต่ Googlebot เห็น = อาจถูกมองว่า cloaking -->
<div style="display: none">
  <h1>คำค้นหายอดฮิต SEO เว็บไซต์ 2026 best SEO services Thailand...</h1>
</div>

Google จะมองว่าเป็น manipulation และ devalue content นั้น

ข้อยกเว้น: ใช้ display: none สำหรับ:

  • Tab content ที่ user สามารถ click ดูได้
  • Modal content
  • Mobile menu

13.4 ข้อผิดพลาดที่ 4: ไม่มี Sitemap หรือ Sitemap ผิด

SPA หลายเว็บลืม generate sitemap หรือ generate ผิด

ตัวอย่าง sitemap.xml ที่ถูกต้อง:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://example.com/</loc>
    <lastmod>2026-06-20</lastmod>
    <changefreq>weekly</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://example.com/blog/javascript-seo-2026</loc>
    <lastmod>2026-06-20</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.8</priority>
  </url>
</urlset>

Auto-generate ใน Astro:

// astro.config.mjs
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://example.com',
  integrations: [sitemap()],
});

13.5 ข้อผิดพลาดที่ 5: ใช้ Service Worker ที่ Cache HTML แบบไม่ถูกต้อง

Service Worker ที่ aggressive caching อาจ serve HTML เก่าให้ Googlebot

// แย่: Cache HTML นานเกินไป
self.addEventListener('fetch', event => {
  if (event.request.headers.get('accept').includes('text/html')) {
    event.respondWith(
      caches.match(event.request).then(response => response || fetch(event.request))
    );
  }
});

วิธีแก้: ใช้ “Network First” สำหรับ HTML

self.addEventListener('fetch', event => {
  if (event.request.headers.get('accept').includes('text/html')) {
    event.respondWith(
      fetch(event.request)
        .then(response => {
          const clone = response.clone();
          caches.open('html-cache').then(cache => cache.put(event.request, clone));
          return response;
        })
        .catch(() => caches.match(event.request))
    );
  }
});

14. FAQ — คำถามที่พบบ่อยเกี่ยวกับ JavaScript SEO

Q1: ถ้าผมใช้ React Create React App (CRA) จะมีปัญหา SEO ไหม?

A: มีปัญหาแน่นอน เพราะ CRA เป็น CSR pure — Googlebot crawl ครั้งแรกจะเห็นแต่ <div id="root"></div> ทำให้ index ช้า และ search engine อื่นๆ (Bing, DuckDuckGo) อาจไม่เห็น content เลย แนะนำให้ migrate ไป Next.js หรือ Astro

Q2: Next.js กับ Astro อันไหนดีกว่ากันสำหรับ SEO?

A: ขึ้นอยู่กับประเภทเว็บ:

  • Marketing site, Blog, Documentation: Astro ดีกว่า เพราะ SSG default + Islands Architecture
  • E-commerce, Dashboard, App ที่ต้อง dynamic: Next.js ดีกว่า เพราะรองรับ SSR/ISR
  • ทั้งสอง framework SEO score เท่ากันถ้า setup ถูก แต่ Astro มี performance ดีกว่าโดย default

Q3: Googlebot ปี 2026 ใช้ Chromium version อะไร?

A: Googlebot ปัจจุบันใช้ Chromium 130+ ซึ่งรองรับ JavaScript features ส่วนใหญ่ที่ใช้ใน production แล้ว แต่ก็ยังต้องระวัง features ใหม่ๆ เช่น View Transitions API, CSS Container Queries บางตัว — แนะนำให้ test ด้วย URL Inspection Tool เสมอ

Q4: ถ้าใช้ Single Page App ทั้งหมด ทำ Schema Markup ยังไงให้ Google เห็น?

A: 3 ข้อสำคัญ:

  1. Render schema ใน server-side ไม่ใช่ inject ด้วย JS หลัง page load
  2. ใช้ JSON-LD เท่านั้น ไม่ใช้ Microdata หรือ RDFa
  3. Test ด้วย Rich Results Test เพื่อ verify ว่า Google parse ได้ถูก หาก SPA ของคุณเป็น CSR pure ให้ใช้ pre-rendering service หรือ migrate ไป SSR/SSG

Q5: Hydration Mismatch คืออะไร และจะแก้ยังไง?

A: Hydration Mismatch เกิดเมื่อ HTML ที่ server render ไม่ตรงกับที่ client render ครั้งแรก สาเหตุที่พบบ่อย:

  • ใช้ Date.now() หรือ Math.random() ใน render
  • ใช้ localStorage หรือ window ก่อน mount
  • Browser extension แก้ HTML

วิธีแก้:

// React
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return null; // Skip render ที่ depend on client state

// Next.js
'use client';
import { useEffect, useState } from 'react';

Q6: จะรู้ได้ยังไงว่า Googlebot render หน้าเว็บผมสำเร็จหรือไม่?

A: ใช้ 3 วิธีนี้รวมกัน:

  1. Google Search Console URL Inspection — ดู “Rendered HTML” และ “Screenshot”
  2. Mobile-Friendly Test — ดู rendered content
  3. Server log analysis — ดู Googlebot user agent ว่า fetch resource อะไรบ้าง

ถ้าทั้ง 3 วิธีแสดงว่า content ครบ → Googlebot render สำเร็จ

Q7: Vue 3 กับ Nuxt 3 ทำ SEO ได้ดีพอกับ React + Next.js หรือไม่?

A: ดีพอกัน หรืออาจดีกว่าด้วยซ้ำ เพราะ:

  • Nuxt 3 รองรับ SSR/SSG/ISR/Hybrid rendering
  • Auto-import ทำให้โค้ดสะอาด
  • useSeoMeta composable ใช้ง่ายกว่า next/head
  • Bundle size เล็กกว่า Next.js
  • Image optimization ในตัว ที่ Southern Whale เรา recommend Vue + Nuxt 3 สำหรับลูกค้าที่ทีมไม่ถนัด React มาก

Q8: เว็บผม Bounce rate สูงมาก เพราะ SPA โหลดช้า ควรทำยังไง?

A: 5 ขั้นตอนที่ทำได้ทันที:

  1. Audit ด้วย PageSpeed Insights — ดูคะแนน LCP, INP, CLS
  2. Code splitting — โหลดเฉพาะ JS ที่ใช้ในหน้านั้น
  3. Lazy load images ด้วย native loading="lazy"
  4. Implement CDN เช่น Cloudflare (อ่าน คู่มือ Cloudflare ของเรา)
  5. พิจารณา migrate ไป SSG ถ้า content ส่วนใหญ่เป็น static

ถ้าทำเองยาก ติดต่อเราที่ Southern Whale SEO Services เราช่วย audit ฟรี


15. สรุป

JavaScript SEO ในปี 2026 ยังคงเป็นความท้าทายสำคัญที่นักพัฒนาและเจ้าของเว็บไซต์ต้องเข้าใจ แม้ Googlebot จะ render JavaScript ได้ดีขึ้นมาก แต่:

เรียนรู้สำคัญที่คุณควรจำ:

  1. Googlebot มี 2 wave crawling — content ใน HTML ดิบ index ก่อน, JS render ทีหลัง
  2. Render budget จำกัด ประมาณ 5-10 วินาที ถ้าเกิน content อาจหาย
  3. CSR ไม่เหมาะกับ SEO — ใช้ SSR, SSG, หรือ ISR แทน
  4. Astro คือ SEO King ในปี 2026 สำหรับ marketing/blog
  5. Next.js, Nuxt 3, SvelteKit ดีสำหรับ app ที่ต้อง dynamic
  6. Internal links ต้องเป็น <a href> จริง ไม่ใช่ onClick
  7. Schema markup ใช้ JSON-LD render ใน server-side
  8. Test ทุกหน้าด้วย Google Search Console URL Inspection

Migration จาก CSR → SSG เห็นผลจริง — ลูกค้า Southern Whale เพิ่ม traffic +210% ภายใน 4 เดือน หลัง migrate จาก React CSR ไป Astro SSG

ถ้าเว็บไซต์ของคุณกำลังใช้ React, Vue, หรือ Angular แบบ CSR และต้องการ traffic จาก Google มากขึ้น อย่ารอ — เริ่ม audit เว็บคุณวันนี้ด้วยเครื่องมือที่เราแนะนำในบทความนี้ หรือถ้าต้องการให้ทีมมืออาชีพช่วย ติดต่อ Southern Whale ที่ หน้า Contact เราพร้อมช่วยคุณ migrate และ optimize เพื่อให้ Google มองเห็นเว็บคุณอย่างเต็มศักยภาพ

บทความที่เกี่ยวข้อง:

หวังว่าบทความนี้จะช่วยให้คุณเข้าใจ JavaScript SEO ในปี 2026 และนำไปประยุกต์ใช้กับเว็บไซต์ของคุณได้ ขอให้โชคดีกับการทำ SEO ครับ!

คีย์เวิร์ดที่เกี่ยวข้อง

javascript seo, react seo, spa seo, vue seo, angular seo, ssr vs csr, googlebot render, hydration seo

บทความที่เกี่ยวข้อง