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__/
86 lines
2.8 KiB
Plaintext
86 lines
2.8 KiB
Plaintext
---
|
|
/**
|
|
* 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} />
|