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:
85
src/components/seo/SEO.astro
Normal file
85
src/components/seo/SEO.astro
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
/**
|
||||
* SEO.astro — Reusable <head> tags for OG, Twitter, canonical, robots.
|
||||
*
|
||||
* Place inside <head>. All props are optional except title/description
|
||||
* (which the parent layout owns). Designed to be rendered once per page
|
||||
* via BaseLayout.astro.
|
||||
*/
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
/** Path-only or absolute URL. Defaults to /images/og/default-og.jpg */
|
||||
ogImage?: string;
|
||||
/** Override canonical URL. Default: current pathname */
|
||||
canonical?: string;
|
||||
/** og:type — "website" for pages, "article" for blog */
|
||||
ogType?: 'website' | 'article' | 'product';
|
||||
/** Article published time (ISO) — only used when ogType=article */
|
||||
publishedTime?: string;
|
||||
/** Article author — only used when ogType=article */
|
||||
author?: string;
|
||||
/** Indexing policy. Default: 'index, follow' */
|
||||
robots?: string;
|
||||
/** Override site_name for og:site_name */
|
||||
siteName?: string;
|
||||
/** Locale tag — default th_TH */
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
ogImage = '/images/og/default-og.jpg',
|
||||
canonical,
|
||||
ogType = 'website',
|
||||
publishedTime,
|
||||
author,
|
||||
robots = 'index, follow',
|
||||
siteName = 'ดีล พลัส เทค',
|
||||
locale = 'th_TH',
|
||||
} = Astro.props;
|
||||
|
||||
const siteUrl = Astro.site ?? new URL(Astro.url.origin);
|
||||
const origin = siteUrl.origin;
|
||||
|
||||
// Build absolute URL for ogImage (handle both path-only and full URL inputs)
|
||||
const isAbsolute = /^https?:\/\//.test(ogImage);
|
||||
const fullOgImage = isAbsolute ? ogImage : new URL(ogImage, origin).toString();
|
||||
|
||||
// Canonical: explicit override OR current path
|
||||
const canonicalPath = canonical ?? Astro.url.pathname;
|
||||
const fullCanonical = new URL(canonicalPath, origin).toString();
|
||||
---
|
||||
|
||||
<!-- Canonical -->
|
||||
<link rel="canonical" href={fullCanonical} />
|
||||
|
||||
<!-- Robots -->
|
||||
<meta name="robots" content={robots} />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content={ogType} />
|
||||
<meta property="og:site_name" content={siteName} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:url" content={fullCanonical} />
|
||||
<meta property="og:image" content={fullOgImage} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:image:alt" content={title} />
|
||||
<meta property="og:locale" content={locale} />
|
||||
|
||||
{ogType === 'article' && publishedTime && (
|
||||
<meta property="article:published_time" content={publishedTime} />
|
||||
)}
|
||||
{ogType === 'article' && author && (
|
||||
<meta property="article:author" content={author} />
|
||||
)}
|
||||
|
||||
<!-- 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={fullOgImage} />
|
||||
<meta name="twitter:image:alt" content={title} />
|
||||
Reference in New Issue
Block a user