HTML บอกคนอ่าน JSON-LD บอก Google
เปิดหน้าสินค้าหน้าหนึ่ง เราเห็นทันทีว่านี่คือกาแฟคั่วกลาง ราคา 320 บาท ยังมีของ แต่โปรแกรมที่อ่าน HTML เห็นเลข 320 เป็นแค่ข้อความใน <span> ไม่รู้ว่าเป็นราคา น้ำหนัก หรือยอดกดถูกใจ Google ต้องเดาเอาจากหน้าตาของหน้า
JSON-LD (JSON for Linking Data) เป็นทางบอกตรง ๆ เราเขียนข้อมูลของหน้าเป็น JSON ใส่ไว้ในแท็ก script ชนิด application/ld+json เบราว์เซอร์ไม่รันและไม่แสดง คนเปิดเว็บจึงไม่เห็น แต่ Google กับ search engine อื่นอ่านได้
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "กาแฟคั่วกลาง 250 กรัม",
"image": "https://shop.example.com/img/medium-roast.jpg",
"description": "เมล็ดอาราบิกาจากดอยช้าง คั่วกลาง หอมช็อกโกแลต",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"reviewCount": "126"
},
"offers": {
"@type": "Offer",
"price": "320",
"priceCurrency": "THB",
"availability": "https://schema.org/InStock"
}
}
</script>
key ที่ขึ้นต้นด้วย @ เป็นคำของ JSON-LD เอง ตามสเปก JSON-LD 1.1 ของ W3C:
@contextบอกว่าชื่อ key ในก้อนนี้มาจากคลังคำไหน แทบทุกเว็บใช้https://schema.org@typeบอกว่าสิ่งนี้คืออะไร เช่นProduct,BlogPosting,LocalBusiness@idตั้งชื่อให้สิ่งนั้น ก้อนอื่นจะได้อ้างถึงได้ (ตัวอย่างนี้ยังไม่ใช้ เดี๋ยวจะเห็นข้างล่าง)
ส่วน name, offers, price เป็นคำของ schema.org แต่ละ type มี property ของตัวเอง เปิดดูได้จากหน้าของ type นั้น เช่น schema.org/Product ค่าที่มีตัวเลือกตายตัวอย่าง availability ก็เขียนเป็น URL ของ schema.org (https://schema.org/InStock)
Google แนะนำ JSON-LD มากกว่า Microdata
ข้อมูลแบบนี้เรียกรวม ๆ ว่า structured data คือข้อมูลที่จัดเป็นโครงสร้างให้เครื่องอ่าน เขียนได้สามแบบ Google รองรับทั้ง JSON-LD, Microdata และ RDFa แต่แนะนำ JSON-LD ถ้าระบบของเราทำได้ ใครเคยแกะธีม WordPress หรือเทมเพลตร้านค้าเก่า ๆ อาจเคยเห็น itemprop แทรกอยู่ใน HTML นั่นคือ Microdata
<!-- Microdata: ใส่ attribute ปนลงไปใน HTML ที่แสดงผล -->
<div itemscope itemtype="https://schema.org/Product">
<h1 itemprop="name">กาแฟคั่วกลาง 250 กรัม</h1>
<div itemprop="offers" itemscope itemtype="https://schema.org/Offer">
<span itemprop="price" content="320">฿320</span>
<meta itemprop="priceCurrency" content="THB">
</div>
</div>
<!-- JSON-LD: HTML เดิมไม่ต้องแตะ ข้อมูลแยกไปอยู่ก้อนเดียว -->
<h1>กาแฟคั่วกลาง 250 กรัม</h1>
<span>฿320</span>
<script type="application/ld+json">
{ "@context": "https://schema.org", "@type": "Product", "name": "กาแฟคั่วกลาง 250 กรัม",
"offers": { "@type": "Offer", "price": "320", "priceCurrency": "THB" } }
</script>
Microdata ผูกอยู่กับโครงสร้าง HTML พอมีคนย้าย div ข้อมูลก็พังตาม JSON-LD อยู่แยกเป็นก้อน สร้างจากข้อมูลใน database ได้ตรง ๆ ไม่ต้องยุ่งกับ view และ Google ยังอ่าน JSON-LD ที่ JavaScript ใส่เข้ามาทีหลังได้ด้วย
Google อาจโชว์ราคาและดาวรีวิวในผลค้นหา
ผลที่เห็นได้ชัดที่สุดของ JSON-LD คือ rich result ผลค้นหาที่มีข้อมูลเพิ่มมาจากชื่อกับคำอธิบาย

หน้าเดียวกัน ถ้า Google อ่านออกว่าเป็นสินค้า อาจแสดงดาว ราคา และสถานะสต็อกใต้ผลค้นหา
แต่ใส่ครบแล้ว Google ก็ไม่รับปากว่าจะโชว์ แนวทาง structured data ของ Google เขียนไว้ชัดว่า ผ่าน Rich Results Test แล้วก็ยังไม่แน่ว่าจะได้แสดง อัลกอริทึมเลือกเองว่าจะโชว์ผลแบบไหน และ Google ไม่เคยบอกว่า structured data ช่วยให้อันดับขึ้น ที่ได้แน่ ๆ คือ Google เข้าใจหน้าเราถูก และหน้าเราเข้าเกณฑ์ที่จะได้แสดงแบบพิเศษ
type ที่ใช้บ่อยในเว็บทั่วไป
Google รวบรวม type ที่รองรับไว้ในแกลเลอรี structured data ตัวที่เว็บบริษัท ร้านค้า และบล็อกได้ใช้บ่อย มีประมาณนี้
| type | ใส่ในหน้า | Google อ่านอะไรไป |
|---|---|---|
Article, BlogPosting |
บทความ ข่าว บล็อก | ชื่อเรื่อง รูป วันที่เผยแพร่ วันที่แก้ไข |
Product |
หน้าสินค้า | ราคา สต็อก ดาวรีวิว |
LocalBusiness |
หน้าร้าน คลินิก สาขา | ที่อยู่ เบอร์โทร เวลาเปิดปิด |
BreadcrumbList |
ทุกหน้าที่อยู่ลึกกว่าหน้าแรก | เส้นทางหน้าที่โชว์แทน URL |
Organization |
หน้าแรก หรือหน้าเกี่ยวกับเรา | ชื่อ โลโก้ และบัญชีโซเชียลของบริษัท |
Event, JobPosting, Recipe |
อีเวนต์ ประกาศรับสมัครงาน สูตรอาหาร | วันเวลา สถานที่ เงินเดือน วัตถุดิบ |
รายการนี้ Google ตัดออกเรื่อย ๆ ก่อนลงแรงกับ type ไหน เปิดแกลเลอรีดูก่อนว่ายังอยู่ไหม
- FAQ หรือกล่องคำถามที่กดขยายได้ใต้ผลค้นหา Google เลิกโชว์ให้ทุกเว็บตั้งแต่ 7 พฤษภาคม 2026 หลังจากจำกัดไว้เฉพาะเว็บราชการกับเว็บสุขภาพตั้งแต่ปี 2023 ส่วน HowTo หายไปตั้งแต่รอบนั้น
- อีก 7 type เช่น Course Info, Claim Review, Estimated Salary และ Vehicle Listing Google ประกาศเลิกโชว์เมื่อมิถุนายน 2025
- Breadcrumb บนมือถือ Google โชว์แค่ชื่อโดเมนตั้งแต่มกราคม 2025 เส้นทางหน้ายังเห็นบนเดสก์ท็อป
ถ้าเว็บเราใช้ WordPress ปลั๊กอิน SEO อย่าง Yoast SEO หรือ Rank Math ใส่ JSON-LD พื้นฐานให้อยู่แล้ว ลองเปิด view-source ดูก่อนเขียนเพิ่มเอง จะได้ไม่มีสองก้อนที่บอกข้อมูลขัดกัน
ใส่ JSON-LD ในหน้า Blade
สร้างข้อมูลเป็น array ใน controller จาก model ตัวเดียวกับที่หน้าเว็บใช้แสดง ราคาในหน้ากับราคาใน JSON-LD จะได้มาจากช่องเดียวกันเสมอ
public function show(Product $product): View
{
$jsonLd = [
'@context' => 'https://schema.org',
'@type' => 'Product',
'name' => $product->name,
'image' => $product->image_url,
'description' => $product->summary,
'offers' => [
'@type' => 'Offer',
'price' => (string) $product->price,
'priceCurrency' => 'THB',
'availability' => $product->stock > 0
? 'https://schema.org/InStock'
: 'https://schema.org/OutOfStock',
'url' => route('products.show', $product),
],
];
return view('products.show', compact('product', 'jsonLd'));
}
@push('head')
<script type="application/ld+json">@json($jsonLd)</script>
@endpush
<head>
{{-- meta อื่น ๆ --}}
@stack('head')
</head>
พิมพ์ด้วย @json ดีกว่าเขียน {!! json_encode($jsonLd) !!} เอง เพราะ @json ใส่ JSON_HEX_TAG กับ flag อื่นให้ (CompilesJson.php) ตัว < กับ > กลายเป็น < และ > ถ้าคำอธิบายสินค้ามีคนพิมพ์ </script> มา ก็ปิดแท็กแล้วแทรกโค้ดของตัวเองไม่ได้ ถ้าจะเขียน json_encode เองก็ใส่ JSON_HEX_TAG ไว้เสมอ
@json มีสองเรื่องที่ควรรู้
- ส่งเป็นตัวแปร อย่าเขียน array ลงไปตรง ๆ Blade แยกอาร์กิวเมนต์ของ
@jsonด้วยการตัดที่จุลภาค@json(['@type' => 'Product', 'name' => $name])จึงพัง เตรียม array ไว้ใน controller หรือ@phpก่อน - ภาษาไทยออกมาเป็น
ก…อ่านใน view-source ยาก แต่ Google อ่านได้ปกติ ถ้าอยากให้อ่านง่ายก็ส่ง flag เองได้ ขอให้มีJSON_HEX_TAGอยู่ด้วย
<script type="application/ld+json">@json($jsonLd, JSON_HEX_TAG | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)</script>
อีกตัวที่ชื่อคล้ายกันคือ Js::from() ตัวนี้ให้ผลเป็นคำสั่ง JSON.parse(...) ของ JavaScript ใช้ในแท็ก script ธรรมดาได้ แต่ใน application/ld+json ต้องเป็น JSON ล้วน จึงใช้ไม่ได้
เว็บ Inertia กับ React ทำแบบเดียวกัน
เว็บนี้เองก็ใส่ JSON-LD ทุกหน้า สร้าง array ฝั่ง PHP ส่งเป็น prop ให้ React แล้วพิมพ์ใน <Head> ของ Inertia JSON.stringify ไม่แปลง < ให้เหมือน @json เราจึงแปลงเอง
const jsonLdScript = (node) => JSON.stringify(node).replace(/</g, '\\u003c');
{jsonLd.map((node, index) => (
<script
key={index}
head-key={`ld:${index}`}
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: jsonLdScript(node) }}
/>
))}
เว็บนี้เปิด SSR ไว้ (วิธีการทำงานของ Inertia SSR) JSON-LD จึงอยู่ใน HTML ตั้งแต่ครั้งแรกที่ server ส่งมา ถ้าเป็น SPA ที่ render ฝั่ง browser อย่างเดียว Google ก็ยังอ่านได้ตามที่บอกไว้ข้างบน แต่ search engine หรือบอทตัวอื่นอาจไม่รัน JavaScript
ใช้ @id ผูกข้อมูลข้ามก้อน
หน้าแรกของเว็บนี้มีก้อน Organization ที่ตั้ง @id ไว้ ส่วนโพสต์ทุกหน้าอ้าง @id เดียวกันเป็นผู้เขียนและผู้เผยแพร่ Google จึงรู้ว่าบทความทุกชิ้นเป็นของบริษัทเดียวกัน โดยไม่ต้องใส่โลโก้กับลิงก์โซเชียลซ้ำทุกหน้า
[
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://phattarachai.dev#organization",
"name": "Phattarachai Artisan",
"url": "https://phattarachai.dev",
"logo": "https://phattarachai.dev/apple-touch-icon.png",
"sameAs": ["https://github.com/phattarachai"]
},
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "JSON-LD คืออะไร ใส่ในเว็บ Laravel ยังไงให้ Google อ่านหน้าเว็บเราออก",
"datePublished": "…",
"dateModified": "…",
"author": { "@type": "Organization", "@id": "https://phattarachai.dev#organization", "name": "Phattarachai Artisan" }
}
]
ตัดมาบางส่วน ก้อนแรกอยู่หน้าแรก ก้อนที่สองอยู่หน้าโพสต์นี้ ลองเปิด view-source หน้านี้ดูของจริงได้
ตรวจก่อนขึ้นจริง
- Rich Results Test ของ Google วาง URL หรือวางโค้ดก็ได้ บอกว่าหน้านี้เข้าเกณฑ์ rich result แบบไหน และขาด property ที่จำเป็นตัวไหน ถ้ายังเป็นเว็บในเครื่อง (
localhost) ให้ copy ก้อน JSON-LD ไปวาง - Schema Markup Validator ตรวจกับคำศัพท์ทั้งหมดของ schema.org ไม่ใช่แค่ type ที่ Google ใช้ เหมาะกับเช็กว่าสะกด property ถูกไหม
- Search Console หลังขึ้น production มีรายงาน rich result แยกตาม type บอกว่ามีกี่หน้าที่ข้อมูลมีปัญหา และเครื่องมือ URL Inspection เช็กได้ว่า Google เจอ JSON-LD ในหน้าไหนแล้วบ้าง
ข้อมูลใน JSON-LD ต้องตรงกับที่หน้าเว็บโชว์
ข้อนี้พลาดแล้วเสียกว่าไม่ใส่ แนวทางของ Google ห้ามใส่ข้อมูลที่คนเปิดหน้าไม่เห็น และห้ามใส่ข้อมูลที่ไม่ตรงกับเนื้อหา
- ใส่
aggregateRatingได้เมื่อหน้านั้นแสดงรีวิวจริง ไม่ใช่ใส่ 4.9 ดาวไว้หวังให้ผลค้นหาดูดี - ราคาและสต็อกต้องตรงกับหน้า ถ้าเปลี่ยนราคาในหลังบ้านแล้ว JSON-LD ยังเป็นราคาเก่า ก็ถือว่าไม่ตรง การสร้างจาก model ตัวเดียวกับ view อย่างข้างบนช่วยกันเรื่องนี้ได้
- อย่าทำหน้าเปล่า ๆ ขึ้นมาเพื่อใส่ JSON-LD อย่างเดียว
ถ้า Google เจอว่าทำผิดกฎ อาจโดน manual action ผลคือหน้านั้นหมดสิทธิ์แสดง rich result ส่วนอันดับในผลค้นหาปกติไม่กระทบ เช็กได้ในรายงาน Manual Actions ของ Search Console
ใส่ JSON-LD แล้ว อีกอย่างที่ช่วยให้ Google เจอหน้าเราครบคือ sitemap ถ้ายังไม่ได้ทำ ลองอ่านสร้าง sitemap.xml ใน Laravel ด้วย spatie/laravel-sitemap ต่อได้ครับ






