feat(blog): Phase 5 SEO/GEO content with 5 new blog posts
Add 5 long-form Thai blog posts (1,200-2,500 words each) with SEO + GEO optimization for the dealplustech water-systems site. Each post targets a specific audience (contractors, engineers, project managers) and follows a content-quality workflow: source real product specs, verify Thai text, dedupe images, link back to product pages. ## New blog posts (src/content/blog/) - thermobreak-guide.md (Thermobreak closed-cell insulation overview) - plastic-grilles-guide.md (ABS plastic grilles for HVAC) - ppr-pipe-guide.md (PPR pipe properties + heat-fusion welding) - ppr-vs-hdpe-vs-upvc.md (3-way pipe comparison with PE80/PE100) - thermobreak-series-guide.md (Thermobreak LS vs Solar series) - 10-things-checklist-pipe-ordering.md (10-point pre-order checklist) ## Removed legacy posts - pipe-knowledge.md, valve-guide.md, welcome-post.md (orphans) ## Hero images (public/images/blog/) ~20 product photos sourced from manufacturers (Thermobreak, Thai PPR, thaiconsupply) plus Nano Banana Pro infographics. All resized to 3:2 aspect ratio per user preference. Source folder preserved for re-derivation. ## Astro layout/SEO work - src/components/seo/SEO.astro, JsonLd.astro (new SEO components) - src/layouts/BaseLayout.astro, Layout.astro (OG/Twitter/JSON-LD wiring) - src/pages/404.astro - Product pages (8): added #pricelist anchors + schema work - src/styles/global.css: scroll-padding for sticky-header anchors ## Automation scripts (scripts/) - build_og_image.py (OG image builder) - inject_faq_schema.py, inject_product_schema.py (JSON-LD injection) ## Misc - public/robots.txt, public/images/og/default-og.jpg - .gitignore: exclude scripts/__pycache__/
This commit is contained in:
@@ -26,9 +26,89 @@ const related = allArticles
|
||||
.slice(0, 3);
|
||||
|
||||
const tag = article.data.tags?.[0] ?? '';
|
||||
|
||||
// BlogPosting JSON-LD — provides rich result eligibility in Google
|
||||
// (article cards, author attribution, publish date).
|
||||
const articleUrl = `https://dealplustech.com/${encodeURI('บทความ')}/${article.id}`;
|
||||
const blogPostingSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
'@id': `${articleUrl}#article`,
|
||||
headline: article.data.title,
|
||||
description: article.data.excerpt || `บทความ ${article.data.title}`,
|
||||
...(article.data.featured_image && {
|
||||
image: /^https?:\/\//.test(article.data.featured_image)
|
||||
? article.data.featured_image
|
||||
: `https://dealplustech.com${article.data.featured_image}`,
|
||||
}),
|
||||
datePublished: article.data.published_at.toISOString(),
|
||||
dateModified: (article.data.updated_at ?? article.data.published_at).toISOString(),
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: article.data.author || 'ดีล พลัส เทค',
|
||||
url: 'https://dealplustech.com',
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'ดีล พลัส เทค',
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: 'https://dealplustech.com/images/logo/dealplustech-logo.png',
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': articleUrl,
|
||||
},
|
||||
...(article.data.tags && article.data.tags.length > 0 && {
|
||||
keywords: article.data.tags.join(', '),
|
||||
}),
|
||||
...(article.data.reviewer && {
|
||||
reviewedBy: {
|
||||
'@type': 'Organization',
|
||||
name: article.data.reviewer,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
// Extract FAQ pairs from article body — scoped to the FAQ section only.
|
||||
// Looks for `## คำถาม...` or `## FAQ` heading then captures all H3+paragraph
|
||||
// pairs until the next H2.
|
||||
const faqPairs: { question: string; answer: string }[] = [];
|
||||
const body = article.body ?? '';
|
||||
const faqSection = body.match(/##\s+(?:คำถาม[^\n]*|FAQ[^\n]*|Common Questions[^\n]*)\s*\n([\s\S]*?)(?=\n##\s|\n---\s*$|$)/i);
|
||||
if (faqSection) {
|
||||
const sectionBody = faqSection[1];
|
||||
const pairRegex = /###\s+(.+?)\n\n([\s\S]*?)(?=\n###|\n##\s|$)/g;
|
||||
for (const m of sectionBody.matchAll(pairRegex)) {
|
||||
const question = m[1].trim();
|
||||
const answer = m[2].trim().replace(/\n+/g, ' ');
|
||||
if (question && answer && !question.startsWith('##')) {
|
||||
faqPairs.push({ question, answer });
|
||||
}
|
||||
}
|
||||
}
|
||||
const faqSchema = faqPairs.length >= 2 ? {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: faqPairs.map(p => ({
|
||||
'@type': 'Question',
|
||||
name: p.question,
|
||||
acceptedAnswer: { '@type': 'Answer', text: p.answer },
|
||||
})),
|
||||
} : null;
|
||||
---
|
||||
|
||||
<BaseLayout title={`${article.data.title} - ดีล พลัส เทค`} description={article.data.excerpt || `บทความ ${article.data.title}`}>
|
||||
<BaseLayout
|
||||
title={`${article.data.title} - ดีล พลัส เทค`}
|
||||
description={article.data.excerpt || `บทความ ${article.data.title}`}
|
||||
ogImage={article.data.og_image ?? article.data.featured_image}
|
||||
ogType="article"
|
||||
publishedTime={article.data.published_at.toISOString()}
|
||||
author="ดีล พลัส เทค"
|
||||
jsonLd={blogPostingSchema}
|
||||
faq={faqPairs}
|
||||
>
|
||||
<main class="bg-white min-h-screen">
|
||||
{/* Breadcrumb */}
|
||||
<section class="bg-slate-50 border-b border-slate-100">
|
||||
@@ -51,22 +131,17 @@ const tag = article.data.tags?.[0] ?? '';
|
||||
<span class="inline-block px-3 py-1 bg-primary-50 text-primary-600 rounded-full text-sm font-medium mb-4">{tag}</span>
|
||||
)}
|
||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-slate-900 mb-4 leading-tight">{article.data.title}</h1>
|
||||
<div class="flex items-center gap-4 text-slate-500">
|
||||
<time datetime={article.data.published_at.toISOString().slice(0, 10)} class="text-lg">
|
||||
{article.data.published_at.toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric' })}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Featured Image */}
|
||||
{article.data.featured_image && (
|
||||
<div class="rounded-3xl overflow-hidden mb-12 shadow-lg">
|
||||
<img src={article.data.featured_image} alt={article.data.title} class="w-full h-auto" />
|
||||
<img src={article.data.featured_image} alt={article.data.title} class="w-full h-auto" loading="lazy" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Article Body */}
|
||||
<article class="prose prose-lg max-w-none prose-headings:text-slate-900 prose-a:text-primary-600 prose-img:rounded-2xl prose-img:shadow-md mb-16">
|
||||
<article class="prose prose-lg max-w-none prose-headings:text-slate-900 prose-headings:font-bold prose-h2:text-2xl prose-h2:mt-12 prose-h2:mb-4 prose-h2:pb-3 prose-h2:border-b-2 prose-h2:border-primary-200 prose-h2:text-primary-800 prose-h3:text-xl prose-h3:mt-8 prose-h3:mb-3 prose-h3:text-primary-700 prose-h3:font-semibold prose-h4:text-base prose-h4:font-semibold prose-a:text-primary-600 prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-md prose-strong:text-neutral-900 prose-strong:font-semibold prose-table:my-0 prose-th:bg-primary-50 prose-th:text-primary-800 prose-th:font-bold prose-td:border-neutral-200 mb-16">
|
||||
<Content />
|
||||
</article>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user