บทนำ: ทำไมคุณยังเสีย 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 ของคุณ:
- โหลดช้า (เกิน 5 วินาที) — Googlebot อาจ render ไม่ทัน content
- มี API call ที่ช้า — content ที่ต้อง fetch อาจไม่ถูก index
- มี dependency ซับซ้อน — บาง component อาจไม่ render เลย
- 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
- Code splitting — โหลดเฉพาะ JS ที่ใช้
- Tree shaking — กำจัด dead code
- Lazy load images — แต่ใช้ native
loading="lazy"ไม่ใช่ JavaScript - Preload critical resources —
<link rel="preload"> - Defer non-critical scripts —
<script defer>หรือ<script async> - 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
| Strategy | SEO | Performance | Build Time | Server Cost | Update Frequency | เหมาะกับ |
|---|---|---|---|---|---|---|
| CSR (Client-Side Rendering) | แย่ | แย่ตอนแรก | เร็ว | ต่ำ | Real-time | App ที่ต้อง login |
| SSR (Server-Side Rendering) | ดีมาก | ปานกลาง | ทันที | สูง | Real-time | E-commerce, dashboard |
| SSG (Static Site Generation) | ดีที่สุด | ดีที่สุด | นาน | ต่ำสุด | ตอน build | Blog, 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
| Framework | Default | SSR Solution | SSG Support | SEO Score | Hydration Cost | เหมาะกับ |
|---|---|---|---|---|---|---|
| React | CSR | Next.js | Next.js / Gatsby | ดี (with Next) | สูง | App ทุกประเภท |
| Vue | CSR | Nuxt 3 | Nuxt 3 / VitePress | ดี (with Nuxt) | ปานกลาง | App, blog |
| Angular | CSR | Angular Universal | Scully | ปานกลาง | สูงที่สุด | Enterprise app |
| Svelte | CSR | SvelteKit | SvelteKit | ดีมาก | ต่ำ | App ขนาดเล็ก-กลาง |
| Astro | SSG | Built-in | Built-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 เห็นหน้าเว็บคุณยังไง
วิธีใช้:
- เปิด Google Search Console
- ใส่ URL ที่ต้องการตรวจสอบ
- คลิก “Test Live URL”
- ดู “Rendered HTML” และ “Screenshot”
- ดู “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:
- Configuration → Spider → Rendering → JavaScript
- ตั้ง User-Agent: Googlebot Smartphone
- ตั้ง AJAX Timeout: 5 seconds (จำลอง render budget)
- 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 วินาทีไหม:
- เปิด DevTools → Network
- Throttling → “Slow 3G”
- Reload
- ดูว่า 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
- ใช้ descriptive anchor text — “JavaScript SEO Guide” ดีกว่า “click here”
- ลิงก์จากหน้า authority — ลิงก์จาก homepage ไปหน้าสำคัญ
- สร้าง topic cluster — รวมหน้าที่เกี่ยวข้องเข้าด้วยกัน
- Internal link จากบทความใหม่ไปบทความเก่า — ส่ง link equity
- 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
- ต้อง render schema ใน HTML ดิบ — ไม่ใช่ inject ด้วย JS หลัง page load
- เนื้อหาใน schema ต้องตรงกับเนื้อหาในหน้า — ไม่งั้นเป็น manual penalty
- Test ด้วย Rich Results Test —
search.google.com/test/rich-results - อย่าใช้ 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 สร้าง
| Framework | Bundle Size (gzip) | Hydration Time (avg) |
|---|---|---|
| Astro (Islands) | 0 KB (default) | ~0ms |
| Svelte | 3-5 KB | ~50ms |
| Vue 3 | 30-40 KB | ~150ms |
| React 18 | 40-50 KB | ~200ms |
| Angular | 100-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 ปัญหาที่เราเจอ
- HTML ดิบเป็น
<div id="root"></div>— Googlebot เห็นแต่ skeleton - Title และ Meta description ใช้ react-helmet — บางหน้า render ไม่ทัน → Googlebot ได้ default title
- Internal links ใช้ React Router
useNavigateแทน<Link>— Googlebot follow ไม่ได้ - Schema markup inject ผ่าน useEffect — บางครั้ง render ไม่ทัน
- Image lazy load ด้วย JS — Googlebot ไม่เห็น image
- Bundle size 1.2MB — slow LCP
- 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
.astrofiles - 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 indexed | 12 / 47 | 45 / 47 | +275% |
| Organic traffic | 2,300/เดือน | 7,150/เดือน | +210% |
| LCP | 5.2s | 1.4s | -73% |
| INP | 480ms | 120ms | -75% |
| Bounce rate | 72% | 38% | -47% |
| Conversion rate | 1.2% | 3.8% | +217% |
| Bundle size | 1.2MB | 45KB | -96% |
12.5 บทเรียนที่ได้
- Astro SSG เหมาะกับเว็บ marketing/blog ที่สุด — ไม่ต้องใช้ JS framework หนัก
- Migration ต้องวางแผน 301 redirect ให้ดี — ไม่งั้นเสีย ranking
- ใช้ Islands Architecture สำหรับ interactive component — ไม่ต้องทิ้ง React ทั้งหมด
- Schema markup ใน SSG = render ทันที = Google เห็นเสมอ
- 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 ข้อสำคัญ:
- Render schema ใน server-side ไม่ใช่ inject ด้วย JS หลัง page load
- ใช้ JSON-LD เท่านั้น ไม่ใช้ Microdata หรือ RDFa
- 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 วิธีนี้รวมกัน:
- Google Search Console URL Inspection — ดู “Rendered HTML” และ “Screenshot”
- Mobile-Friendly Test — ดู rendered content
- 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 ทำให้โค้ดสะอาด
useSeoMetacomposable ใช้ง่ายกว่า next/head- Bundle size เล็กกว่า Next.js
- Image optimization ในตัว ที่ Southern Whale เรา recommend Vue + Nuxt 3 สำหรับลูกค้าที่ทีมไม่ถนัด React มาก
Q8: เว็บผม Bounce rate สูงมาก เพราะ SPA โหลดช้า ควรทำยังไง?
A: 5 ขั้นตอนที่ทำได้ทันที:
- Audit ด้วย PageSpeed Insights — ดูคะแนน LCP, INP, CLS
- Code splitting — โหลดเฉพาะ JS ที่ใช้ในหน้านั้น
- Lazy load images ด้วย native
loading="lazy" - Implement CDN เช่น Cloudflare (อ่าน คู่มือ Cloudflare ของเรา)
- พิจารณา migrate ไป SSG ถ้า content ส่วนใหญ่เป็น static
ถ้าทำเองยาก ติดต่อเราที่ Southern Whale SEO Services เราช่วย audit ฟรี
15. สรุป
JavaScript SEO ในปี 2026 ยังคงเป็นความท้าทายสำคัญที่นักพัฒนาและเจ้าของเว็บไซต์ต้องเข้าใจ แม้ Googlebot จะ render JavaScript ได้ดีขึ้นมาก แต่:
เรียนรู้สำคัญที่คุณควรจำ:
- Googlebot มี 2 wave crawling — content ใน HTML ดิบ index ก่อน, JS render ทีหลัง
- Render budget จำกัด ประมาณ 5-10 วินาที ถ้าเกิน content อาจหาย
- CSR ไม่เหมาะกับ SEO — ใช้ SSR, SSG, หรือ ISR แทน
- Astro คือ SEO King ในปี 2026 สำหรับ marketing/blog
- Next.js, Nuxt 3, SvelteKit ดีสำหรับ app ที่ต้อง dynamic
- Internal links ต้องเป็น
<a href>จริง ไม่ใช่onClick - Schema markup ใช้ JSON-LD render ใน server-side
- 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 มองเห็นเว็บคุณอย่างเต็มศักยภาพ
บทความที่เกี่ยวข้อง:
- Core Web Vitals Guide 2026 — INP, LCP, CLS แบบลึก
- Astro Framework Guide 2026 — ทำไม Astro ถึงเป็น SEO King
- Cloudflare Complete Guide 2026 — CDN ที่ช่วย JavaScript SEO
หวังว่าบทความนี้จะช่วยให้คุณเข้าใจ JavaScript SEO ในปี 2026 และนำไปประยุกต์ใช้กับเว็บไซต์ของคุณได้ ขอให้โชคดีกับการทำ SEO ครับ!