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__/
196 lines
10 KiB
Plaintext
196 lines
10 KiB
Plaintext
---
|
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
|
import { getCollection, render } from 'astro:content';
|
|
|
|
export async function getStaticPaths() {
|
|
const articles = await getCollection('blog');
|
|
return articles.map(article => ({
|
|
params: { slug: article.id },
|
|
props: { article },
|
|
}));
|
|
}
|
|
|
|
const { article } = Astro.props;
|
|
const { Content } = await render(article);
|
|
|
|
// Get related articles (same tags first, then by date, excluding current)
|
|
const allArticles = await getCollection('blog');
|
|
const related = allArticles
|
|
.filter(a => a.id !== article.id)
|
|
.sort((a, b) => {
|
|
const aTagMatch = a.data.tags?.some(t => article.data.tags?.includes(t)) ? 1 : 0;
|
|
const bTagMatch = b.data.tags?.some(t => article.data.tags?.includes(t)) ? 1 : 0;
|
|
if (aTagMatch !== bTagMatch) return bTagMatch - aTagMatch;
|
|
return b.data.published_at.getTime() - a.data.published_at.getTime();
|
|
})
|
|
.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}`}
|
|
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">
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
|
<nav class="text-sm text-slate-500">
|
|
<a href="/" class="hover:text-primary-600 transition-colors">หน้าแรก</a>
|
|
<span class="mx-2">/</span>
|
|
<a href={`/${encodeURI('บทความ')}`} class="hover:text-primary-600 transition-colors">บทความ</a>
|
|
<span class="mx-2">/</span>
|
|
<span class="text-slate-800">{article.data.title}</span>
|
|
</nav>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Article Header */}
|
|
<section class="py-12 lg:py-16">
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="mb-8">
|
|
{tag && (
|
|
<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>
|
|
|
|
{/* 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" loading="lazy" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Article Body */}
|
|
<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>
|
|
|
|
{/* Share Buttons */}
|
|
<div class="border-t border-slate-100 pt-8 mb-8">
|
|
<div class="flex items-center gap-4">
|
|
<span class="text-sm font-medium text-slate-700">แชร์บทความ:</span>
|
|
<a href={`https://line.me/R/msg/text/?${encodeURIComponent(`${article.data.title} - https://dealplustech.com/${encodeURI('บทความ')}/${encodeURIComponent(article.id)}`)}`} target="_blank" rel="noopener" class="inline-flex items-center gap-2 px-4 py-2 bg-green-50 text-green-600 rounded-xl hover:bg-green-100 transition-colors text-sm font-medium">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M19.365 9.863c.349 0 .63.285.63.631 0 .345-.281.63-.63.63H17.61v1.125h1.755c.349 0 .63.283.63.63 0 .344-.281.629-.63.629h-2.386c-.345 0-.627-.285-.627-.629V8.108c0-.344.282-.629.627-.629h2.386c.349 0 .63.285.63.63 0 .349-.281.63-.63.63H17.61v1.125h1.755zm-3.855 3.016c0 .27-.174.51-.432.596-.064.021-.133.031-.199.031-.211 0-.391-.09-.51-.25l-2.443-3.317v2.94c0 .344-.279.629-.631.629-.346 0-.626-.285-.626-.629V8.108c0-.27.173-.51.43-.595.06-.023.136-.033.194-.033.195 0 .375.104.495.254l2.462 3.33V8.108c0-.345.282-.629.63-.629.345 0 .63.284.63.629v4.771zm-5.741 0c0 .344-.282.629-.631.629-.345 0-.627-.285-.627-.629V8.108c0-.345.282-.629.627-.629.349 0 .631.284.631.629v4.771zm-2.466.629H4.917c-.345 0-.63-.285-.63-.629V8.108c0-.345.285-.629.63-.629.348 0 .63.284.63.629v4.141h1.756c.348 0 .629.283.629.63 0 .344-.282.629-.629.629M24 10.314C24 4.943 18.615.572 12 .572S0 4.943 0 10.314c0 4.811 4.27 8.842 10.035 9.608.391.082.923.258 1.058.59.12.301.079.766.038 1.08l-.164 1.02c-.045.301-.24 1.186 1.049.645 1.291-.539 6.916-4.078 9.436-6.975C23.176 14.393 24 12.458 18.062 24 10.314\"/></svg>
|
|
Line
|
|
</a>
|
|
<a href={`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(`https://dealplustech.com/${encodeURI('บทความ')}/${encodeURIComponent(article.id)}`)}`} target="_blank" rel="noopener" class="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-600 rounded-xl hover:bg-blue-100 transition-colors text-sm font-medium">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
|
|
Facebook
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Related Articles */}
|
|
{related.length > 0 && (
|
|
<section class="py-12 lg:py-16 bg-slate-50">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<h2 class="text-2xl font-bold text-slate-900 mb-8">บทความที่เกี่ยวข้อง</h2>
|
|
<div class="grid md:grid-cols-3 gap-8">
|
|
{related.map(rel => (
|
|
<a href={`/${encodeURI('บทความ')}/${encodeURIComponent(rel.id)}`} class="group block bg-white rounded-3xl overflow-hidden border border-slate-100 hover:border-primary-200 hover:shadow-xl transition-all duration-300">
|
|
<div class="aspect-[16/9] bg-slate-100 overflow-hidden">
|
|
{rel.data.featured_image ? (
|
|
<img src={rel.data.featured_image} alt={rel.data.title} class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" loading="lazy" />
|
|
) : (
|
|
<div class="w-full h-full flex items-center justify-center text-slate-300">
|
|
<svg class="w-12 h-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
</svg>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div class="p-5">
|
|
<h3 class="text-base font-bold text-slate-900 group-hover:text-primary-600 transition-colors mb-2 line-clamp-2">{rel.data.title}</h3>
|
|
<p class="text-sm text-slate-600 line-clamp-2">{rel.data.excerpt}</p>
|
|
</div>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)}
|
|
</main>
|
|
</BaseLayout>
|