Add SEO+GEO foundation + blog auto JSON-LD + robots fix
This commit is contained in:
@@ -3,4 +3,38 @@ Allow: /
|
|||||||
Disallow: /google-apps-script/
|
Disallow: /google-apps-script/
|
||||||
Disallow: /_archive/
|
Disallow: /_archive/
|
||||||
|
|
||||||
|
# ── AI Crawlers (GEO — Generative Engine Optimization) ──
|
||||||
|
User-agent: GPTBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: ChatGPT-User
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: ClaudeBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: anthropic-ai
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: PerplexityBot
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Perplexity-User
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Google-Extended
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Bytespider
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: Applebot-Extended
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: cohere-ai
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
User-agent: meta-externalagent
|
||||||
|
Allow: /
|
||||||
|
|
||||||
Sitemap: https://moreminimore.com/sitemap.xml
|
Sitemap: https://moreminimore.com/sitemap.xml
|
||||||
|
|||||||
@@ -9,7 +9,25 @@ const {
|
|||||||
title = 'MoreminiMore',
|
title = 'MoreminiMore',
|
||||||
description = 'MoreminiMore ช่วย SME ดูข้อมูลจริงก่อนตัดสินใจทำเว็บ การตลาด AI หรือระบบอัตโนมัติ',
|
description = 'MoreminiMore ช่วย SME ดูข้อมูลจริงก่อนตัดสินใจทำเว็บ การตลาด AI หรือระบบอัตโนมัติ',
|
||||||
image = '/images/logos/logo-long-black.png',
|
image = '/images/logos/logo-long-black.png',
|
||||||
|
robotsMeta = 'index, follow',
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
|
const siteUrl = new URL('/', Astro.site).toString();
|
||||||
|
const logoUrl = new URL('/images/logos/logo-long-black.png', Astro.site).toString();
|
||||||
|
const ogImageUrl = new URL(image, Astro.site).toString();
|
||||||
|
|
||||||
|
const organizationJsonLd = JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'MoreminiMore',
|
||||||
|
url: siteUrl,
|
||||||
|
logo: logoUrl,
|
||||||
|
description: description,
|
||||||
|
sameAs: [
|
||||||
|
'https://www.facebook.com/moreminimore',
|
||||||
|
'https://www.linkedin.com/company/moreminimore',
|
||||||
|
],
|
||||||
|
});
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -18,6 +36,7 @@ const {
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
|
<meta name="robots" content={robotsMeta} />
|
||||||
<meta name="theme-color" content="#f8f5ea" />
|
<meta name="theme-color" content="#f8f5ea" />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
|
|
||||||
@@ -30,7 +49,7 @@ const {
|
|||||||
<meta property="og:description" content={description} />
|
<meta property="og:description" content={description} />
|
||||||
<meta property="og:url" content={canonicalURL} />
|
<meta property="og:url" content={canonicalURL} />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:image" content={new URL(image, Astro.site).toString()} />
|
<meta property="og:image" content={ogImageUrl} />
|
||||||
<meta property="og:image:width" content="1200" />
|
<meta property="og:image:width" content="1200" />
|
||||||
<meta property="og:image:height" content="630" />
|
<meta property="og:image:height" content="630" />
|
||||||
<meta property="og:locale" content="th_TH" />
|
<meta property="og:locale" content="th_TH" />
|
||||||
@@ -39,7 +58,11 @@ const {
|
|||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
<meta name="twitter:title" content={title} />
|
<meta name="twitter:title" content={title} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
<meta name="twitter:image" content={new URL(image, Astro.site).toString()} />
|
<meta name="twitter:image" content={ogImageUrl} />
|
||||||
|
|
||||||
|
<!-- Organization JSON-LD -->
|
||||||
|
<script type="application/ld+json" set:html={organizationJsonLd}>
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- Favicons -->
|
<!-- Favicons -->
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ const blog = defineCollection({
|
|||||||
category: z.string(),
|
category: z.string(),
|
||||||
tags: z.array(z.string()).default([]),
|
tags: z.array(z.string()).default([]),
|
||||||
draft: z.boolean().default(false),
|
draft: z.boolean().default(false),
|
||||||
|
heroImage: z.string().optional(),
|
||||||
|
og_image: z.string().optional(),
|
||||||
|
author: z.string().optional(),
|
||||||
|
reviewer: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,116 @@ export async function getStaticPaths() {
|
|||||||
|
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
const { Content } = await render(post);
|
const { Content } = await render(post);
|
||||||
|
const siteUrl = new URL('/', Astro.site).toString();
|
||||||
|
|
||||||
|
// --- Date formatting ---
|
||||||
const formattedDate = new Intl.DateTimeFormat('th-TH', {
|
const formattedDate = new Intl.DateTimeFormat('th-TH', {
|
||||||
dateStyle: 'long',
|
dateStyle: 'long',
|
||||||
}).format(post.data.pubDate);
|
}).format(post.data.pubDate);
|
||||||
|
|
||||||
|
// --- Image: og_image > heroImage > default logo ---
|
||||||
|
const ogImage = new URL(
|
||||||
|
post.data.og_image || post.data.heroImage || '/images/logos/logo-long-black.png',
|
||||||
|
Astro.site,
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
// --- FAQPage auto-extraction ---
|
||||||
|
const rawBody = post.body || '';
|
||||||
|
const faqSectionRegex = /##\s*คำถามที่พบบ่อย\s*\n([\s\S]*?)(?=##\s|\Z)/;
|
||||||
|
const faqMatch = rawBody.match(faqSectionRegex);
|
||||||
|
const faqPairs: { question: string; answer: string }[] = faqMatch
|
||||||
|
? [...faqMatch[1].matchAll(/###\s+(.+?)\n\s*\n?(.+?)(?=\n###|\Z)/gs)]
|
||||||
|
.map(([, q, a]) => ({ question: q.trim(), answer: a.trim() }))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// --- Escaped body for JSON-LD safety ---
|
||||||
|
const safeBody = rawBody.replace(/<\//g, '<\\/');
|
||||||
|
|
||||||
|
// --- BlogPosting JSON-LD ---
|
||||||
|
const blogPostingJsonLd = JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@id': `${siteUrl}blog/${post.id}/#blogposting`,
|
||||||
|
'@type': 'BlogPosting',
|
||||||
|
headline: post.data.title,
|
||||||
|
description: post.data.description,
|
||||||
|
datePublished: post.data.pubDate.toISOString(),
|
||||||
|
dateModified: (post.data.updatedDate || post.data.pubDate).toISOString(),
|
||||||
|
...(post.data.author && {
|
||||||
|
author: { '@type': 'Person', name: post.data.author },
|
||||||
|
}),
|
||||||
|
...(post.data.reviewer && {
|
||||||
|
reviewedBy: { '@type': 'Person', name: post.data.reviewer },
|
||||||
|
}),
|
||||||
|
publisher: {
|
||||||
|
'@id': `${siteUrl}#organization`,
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'MoreminiMore',
|
||||||
|
logo: {
|
||||||
|
'@type': 'ImageObject',
|
||||||
|
url: new URL('/images/logos/logo-long-black.png', Astro.site).toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mainEntityOfPage: new URL(`/blog/${post.id}/`, Astro.site).toString(),
|
||||||
|
articleBody: safeBody,
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- BreadcrumbList JSON-LD ---
|
||||||
|
const breadcrumbJsonLd = JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@id': `${siteUrl}blog/${post.id}/#breadcrumb`,
|
||||||
|
'@type': 'BreadcrumbList',
|
||||||
|
itemListElement: [
|
||||||
|
{
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: 1,
|
||||||
|
name: 'บทความ',
|
||||||
|
item: new URL('/blog/', Astro.site).toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: 2,
|
||||||
|
name: post.data.title,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- FAQPage JSON-LD ---
|
||||||
|
const faqPageJsonLd = faqPairs.length > 0
|
||||||
|
? JSON.stringify({
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@id': `${siteUrl}blog/${post.id}/#faq`,
|
||||||
|
'@type': 'FAQPage',
|
||||||
|
mainEntity: faqPairs.map(({ question, answer }) => ({
|
||||||
|
'@type': 'Question',
|
||||||
|
name: question,
|
||||||
|
acceptedAnswer: {
|
||||||
|
'@type': 'Answer',
|
||||||
|
text: answer,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
: '';
|
||||||
---
|
---
|
||||||
|
|
||||||
<PageShell title={`${post.data.title} | MoreminiMore`} description={post.data.description}>
|
<PageShell
|
||||||
|
title={`${post.data.title} | MoreminiMore`}
|
||||||
|
description={post.data.description}
|
||||||
|
image={ogImage}
|
||||||
|
>
|
||||||
|
<!-- JSON-LD: BreadcrumbList -->
|
||||||
|
<script type="application/ld+json" set:html={breadcrumbJsonLd}>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- JSON-LD: BlogPosting -->
|
||||||
|
<script type="application/ld+json" set:html={blogPostingJsonLd}>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- JSON-LD: FAQPage (auto — only if FAQ section found) -->
|
||||||
|
{faqPageJsonLd && (
|
||||||
|
<script type="application/ld+json" set:html={faqPageJsonLd}>
|
||||||
|
</script>
|
||||||
|
)}
|
||||||
|
|
||||||
<article class="blog-article">
|
<article class="blog-article">
|
||||||
<header class="blog-article-hero scene scene-light" data-scene="light">
|
<header class="blog-article-hero scene scene-light" data-scene="light">
|
||||||
<div class="blog-article-heading">
|
<div class="blog-article-heading">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import LegalPageShell from '../components/LegalPageShell.astro';
|
|||||||
<PageShell
|
<PageShell
|
||||||
title="นโยบายความเป็นส่วนตัว | MoreminiMore"
|
title="นโยบายความเป็นส่วนตัว | MoreminiMore"
|
||||||
description="นโยบายความเป็นส่วนตัวของ MoreminiMore — อธิบายข้อมูลที่เราเก็บ วิธีใช้ และสิทธิของท่านตามพระราชบัญญัติคุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562"
|
description="นโยบายความเป็นส่วนตัวของ MoreminiMore — อธิบายข้อมูลที่เราเก็บ วิธีใช้ และสิทธิของท่านตามพระราชบัญญัติคุ้มครองข้อมูลส่วนบุคคล พ.ศ. 2562"
|
||||||
|
robotsMeta="noindex, nofollow"
|
||||||
>
|
>
|
||||||
<section class="page-hero scene scene-light hero-oversized" data-scene="light">
|
<section class="page-hero scene scene-light hero-oversized" data-scene="light">
|
||||||
<p class="eyebrow">นโยบาย</p>
|
<p class="eyebrow">นโยบาย</p>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import LegalPageShell from '../components/LegalPageShell.astro';
|
|||||||
<PageShell
|
<PageShell
|
||||||
title="เงื่อนไขการใช้งาน | MoreminiMore"
|
title="เงื่อนไขการใช้งาน | MoreminiMore"
|
||||||
description="เงื่อนไขการใช้งานเว็บไซต์ MoreminiMore — การยอมรับเงื่อนไข ทรัพย์สินทางปัญญา ข้อห้าม และข้อจำกัดความรับผิด"
|
description="เงื่อนไขการใช้งานเว็บไซต์ MoreminiMore — การยอมรับเงื่อนไข ทรัพย์สินทางปัญญา ข้อห้าม และข้อจำกัดความรับผิด"
|
||||||
|
robotsMeta="noindex, nofollow"
|
||||||
>
|
>
|
||||||
<section class="page-hero scene scene-light hero-oversized" data-scene="light">
|
<section class="page-hero scene scene-light hero-oversized" data-scene="light">
|
||||||
<p class="eyebrow">เงื่อนไข</p>
|
<p class="eyebrow">เงื่อนไข</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user