Add SEO improvements: sitemap, robots.txt, LocalBusiness schema, GA4, llm.txt

- Add dynamic sitemap.xml generation for all pages
- Add robots.txt for search engine crawl directives
- Add LocalBusiness JSON-LD schema for local SEO
- Add BreadcrumbList schema for navigation breadcrumbs
- Add canonical URLs to all product pages
- Add Twitter Cards metadata
- Add Google Analytics 4 integration component
- Create llm.txt with all product data for AI optimization
- Create reusable UI components (Button, Card, Badge)
- Update company address to full Thai address
- Update .env.example with GA4 placeholder
This commit is contained in:
Kunthawat Greethong
2026-02-28 18:10:09 +07:00
parent 3908ddc765
commit 13436b42e5
12 changed files with 923 additions and 2 deletions

View File

@@ -69,11 +69,15 @@ export async function generateMetadata({ params }: Props) {
title,
description: product.description,
keywords: product.keywords?.join(', '),
alternates: {
canonical: product.href,
},
openGraph: {
title: product.name,
description: product.description,
images: [product.image],
type: 'website',
url: `https://dealplustech.co.th${product.href}`,
},
};
}
@@ -123,6 +127,41 @@ function ProductSchema({ product }: { product: ProductCategory }) {
);
}
// BreadcrumbList Schema for SEO
function BreadcrumbSchema({ product }: { product: ProductCategory }) {
const schema = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: 'หน้าแรก',
item: 'https://dealplustech.co.th',
},
{
'@type': 'ListItem',
position: 2,
name: 'สินค้า',
item: 'https://dealplustech.co.th/product/',
},
{
'@type': 'ListItem',
position: 3,
name: product.name,
item: `https://dealplustech.co.th${product.href}`,
},
],
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
// FAQ Schema for SEO
function FAQSchema({ faq }: { faq: FAQItem[] }) {
const schema = {
@@ -177,8 +216,8 @@ function ProductPage({ product }: { product: ProductCategory }) {
<>
{/* Schema.org Structured Data */}
<ProductSchema product={product} />
<BreadcrumbSchema product={product} />
{product.faq && product.faq.length > 0 && <FAQSchema faq={product.faq} />}
<div className="pt-24 pb-16">
<div className="container mx-auto px-4">
{/* Breadcrumb */}

View File

@@ -4,6 +4,7 @@ import '@/styles/globals.css';
import Header from '@/components/layout/Header';
import Footer from '@/components/layout/Footer';
import FloatingContact from '@/components/layout/FloatingContact';
import GoogleAnalytics from '@/components/analytics/GoogleAnalytics';
const kanit = Kanit({
subsets: ['latin', 'thai'],
@@ -20,6 +21,10 @@ export const metadata: Metadata = {
description: 'ดีลพลัสเทค - ผู้เชี่ยวชาญด้านวัสดุท่อและอุปกรณ์ระบบท่อ ท่อพีพีอาร์ ท่อ HDPE ท่อ PVC วาล์ว และอุปกรณ์ต่อท่อครบวงจร',
keywords: ['ท่อพีพีอาร์', 'ท่อ HDPE', 'ท่อ PVC', 'วาล์ว', 'อุปกรณ์ท่อ', 'ดีลพลัสเทค'],
authors: [{ name: 'Deal Plus Tech' }],
metadataBase: new URL('https://dealplustech.co.th'),
alternates: {
canonical: '/',
},
openGraph: {
type: 'website',
locale: 'th_TH',
@@ -27,9 +32,104 @@ export const metadata: Metadata = {
siteName: 'ดีลพลัสเทค',
title: 'ดีลพลัสเทค - ผู้เชี่ยวชาญด้านวัสดุท่อและอุปกรณ์ระบบท่อ',
description: 'ดีลพลัสเทค - ผู้เชี่ยวชาญด้านวัสดุท่อและอุปกรณ์ระบบท่อ ท่อพีพีอาร์ ท่อ HDPE ท่อ PVC วาล์ว และอุปกรณ์ต่อท่อครบวงจร',
images: [
{
url: '/og-image.jpg',
width: 1200,
height: 630,
alt: 'ดีลพลัสเทค - ผู้เชี่ยวชาญด้านวัสดุท่อและอุปกรณ์ระบบท่อ',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@dealplustech',
title: 'ดีลพลัสเทค - ผู้เชี่ยวชาญด้านวัสดุท่อและอุปกรณ์ระบบท่อ',
description: 'ดีลพลัสเทค - ผู้เชี่ยวชาญด้านวัสดุท่อและอุปกรณ์ระบบท่อ ท่อพีพีอาร์ ท่อ HDPE ท่อ PVC วาล์ว และอุปกรณ์ต่อท่อครบวงจร',
images: ['/og-image.jpg'],
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};
// LocalBusiness Schema for SEO
function LocalBusinessSchema() {
const schema = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
'@id': 'https://dealplustech.co.th/#organization',
name: 'ดีลพลัสเทค',
alternateName: 'Deal Plus Tech Co., Ltd.',
description: 'ผู้เชี่ยวชาญด้านวัสดุท่อและอุปกรณ์ระบบท่อ จำหน่ายท่อพีพีอาร์ ท่อ HDPE ท่อ PVC วาล์ว และอุปกรณ์ต่อท่อครบวงจร',
url: 'https://dealplustech.co.th',
logo: 'https://dealplustech.co.th/images/logo.png',
image: 'https://dealplustech.co.th/og-image.jpg',
telephone: '+66-90-555-1415',
email: 'info@dealplustech.co.th',
address: {
'@type': 'PostalAddress',
streetAddress: '9/70 ซอยนครลุง 17',
addressLocality: 'แขวงบางไผ่',
addressRegion: 'เขตบางแค',
addressCountry: 'TH',
postalCode: '10160',
},
geo: {
'@type': 'GeoCoordinates',
latitude: '13.7244',
longitude: '100.4044',
},
openingHoursSpecification: [
{
'@type': 'OpeningHoursSpecification',
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
opens: '08:30',
closes: '17:30',
},
{
'@type': 'OpeningHoursSpecification',
dayOfWeek: 'Saturday',
opens: '08:30',
closes: '12:00',
},
],
priceRange: '$$',
sameAs: [
'https://facebook.com/dealplustech',
'https://line.me/ti/p/@dealplustech',
],
areaServed: {
'@type': 'Country',
name: 'Thailand',
},
knowsAbout: [
'ท่อพีพีอาร์ (PPR Pipe)',
'ท่อ HDPE',
'ท่อ PVC',
'วาล์ว (Valve)',
'อุปกรณ์ระบบท่อ',
'ระบบดับเพลิง',
'ระบบปรับอากาศ HVAC',
],
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
export default function RootLayout({
children,
}: {
@@ -37,7 +137,11 @@ export default function RootLayout({
}) {
return (
<html lang="th" className={kanit.variable}>
<head>
<LocalBusinessSchema />
</head>
<body className="font-sans">
<GoogleAnalytics />
<Header />
<main className="min-h-screen">{children}</main>
<Footer />

118
src/app/sitemap.ts Normal file
View File

@@ -0,0 +1,118 @@
import { MetadataRoute } from 'next';
import { productCategories, portfolioProjects, mainNavigation } from '@/data/site-config';
const BASE_URL = 'https://dealplustech.co.th';
export default function sitemap(): MetadataRoute.Sitemap {
const now = new Date();
// Static pages
const staticPages: MetadataRoute.Sitemap = [
{
url: BASE_URL,
lastModified: now,
changeFrequency: 'weekly',
priority: 1,
},
{
url: `${BASE_URL}/about-us/`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: `${BASE_URL}/services/`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: `${BASE_URL}/product/`,
lastModified: now,
changeFrequency: 'weekly',
priority: 0.9,
},
{
url: `${BASE_URL}/pipe/`,
lastModified: now,
changeFrequency: 'weekly',
priority: 0.8,
},
{
url: `${BASE_URL}/portfolio/`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.7,
},
{
url: `${BASE_URL}/blog/`,
lastModified: now,
changeFrequency: 'weekly',
priority: 0.7,
},
{
url: `${BASE_URL}/contact-us/`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: `${BASE_URL}/join-us/`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.5,
},
{
url: `${BASE_URL}/sales-engineer/`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.5,
},
{
url: `${BASE_URL}/all-projects/`,
lastModified: now,
changeFrequency: 'monthly',
priority: 0.6,
},
];
// Product pages
const productPages: MetadataRoute.Sitemap = productCategories.map((product) => ({
url: `${BASE_URL}${product.href}`,
lastModified: now,
changeFrequency: 'weekly' as const,
priority: 0.8,
}));
// Portfolio pages
const portfolioPages: MetadataRoute.Sitemap = portfolioProjects.map((project) => ({
url: `${BASE_URL}${project.href}`,
lastModified: now,
changeFrequency: 'monthly' as const,
priority: 0.6,
}));
// Blog posts (Thai URLs)
const blogPages: MetadataRoute.Sitemap = [
{
url: `${BASE_URL}/blog/ข้อดี-ท่อ-hdpe/`,
lastModified: now,
changeFrequency: 'monthly' as const,
priority: 0.6,
},
{
url: `${BASE_URL}/blog/ท่อ-ppr-คืออะไร/`,
lastModified: now,
changeFrequency: 'monthly' as const,
priority: 0.6,
},
{
url: `${BASE_URL}/blog/บำรุงรักษาปั๊มน้ำ/`,
lastModified: now,
changeFrequency: 'monthly' as const,
priority: 0.6,
},
];
return [...staticPages, ...productPages, ...portfolioPages, ...blogPages];
}

View File

@@ -0,0 +1,31 @@
'use client';
import Script from 'next/script';
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
export default function GoogleAnalytics() {
if (!GA_MEASUREMENT_ID || GA_MEASUREMENT_ID === 'G-XXXXXXXXXX') {
return null;
}
return (
<>
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_MEASUREMENT_ID}', {
page_title: document.title,
page_location: window.location.href,
});
`}
</Script>
</>
);
}

View File

@@ -0,0 +1,41 @@
import { HTMLAttributes, forwardRef } from 'react';
import { cn } from '@/lib/utils';
interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
variant?: 'default' | 'primary' | 'success' | 'warning' | 'error';
size?: 'sm' | 'md' | 'lg';
}
const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
({ className, variant = 'default', size = 'md', children, ...props }, ref) => {
return (
<span
ref={ref}
className={cn(
'inline-flex items-center font-medium rounded-full',
{
'bg-secondary-100 text-secondary-700': variant === 'default',
'bg-primary-100 text-primary-700': variant === 'primary',
'bg-green-100 text-green-700': variant === 'success',
'bg-yellow-100 text-yellow-700': variant === 'warning',
'bg-red-100 text-red-700': variant === 'error',
},
{
'px-2 py-0.5 text-xs': size === 'sm',
'px-3 py-1 text-sm': size === 'md',
'px-4 py-1.5 text-base': size === 'lg',
},
className
)}
{...props}
>
{children}
</span>
);
}
);
Badge.displayName = 'Badge';
export { Badge };
export type { BadgeProps };

View File

@@ -0,0 +1,42 @@
import { ButtonHTMLAttributes, forwardRef } from 'react';
import { cn } from '@/lib/utils';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
size?: 'sm' | 'md' | 'lg';
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'primary', size = 'md', children, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(
'inline-flex items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 disabled:pointer-events-none disabled:opacity-50',
// Variants
{
'bg-primary-600 text-white hover:bg-primary-700 active:bg-primary-800': variant === 'primary',
'bg-secondary-100 text-secondary-900 hover:bg-secondary-200': variant === 'secondary',
'border-2 border-primary-600 text-primary-600 hover:bg-primary-50': variant === 'outline',
'text-secondary-600 hover:bg-secondary-100': variant === 'ghost',
},
// Sizes
{
'px-3 py-1.5 text-sm rounded': size === 'sm',
'px-4 py-2 text-base rounded-md': size === 'md',
'px-6 py-3 text-lg rounded-lg': size === 'lg',
},
className
)}
{...props}
>
{children}
</button>
);
}
);
Button.displayName = 'Button';
export { Button };
export type { ButtonProps };

View File

@@ -0,0 +1,63 @@
import { HTMLAttributes, forwardRef } from 'react';
import { cn } from '@/lib/utils';
interface CardProps extends HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'bordered' | 'elevated';
}
const Card = forwardRef<HTMLDivElement, CardProps>(
({ className, variant = 'default', children, ...props }, ref) => {
return (
<div
ref={ref}
className={cn(
'bg-white rounded-xl overflow-hidden',
{
'shadow-sm': variant === 'default',
'border border-secondary-200': variant === 'bordered',
'shadow-lg': variant === 'elevated',
},
className
)}
{...props}
>
{children}
</div>
);
}
);
Card.displayName = 'Card';
const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, children, ...props }, ref) => (
<div ref={ref} className={cn('p-4 border-b border-secondary-100', className)} {...props}>
{children}
</div>
)
);
CardHeader.displayName = 'CardHeader';
const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, children, ...props }, ref) => (
<div ref={ref} className={cn('p-4', className)} {...props}>
{children}
</div>
)
);
CardContent.displayName = 'CardContent';
const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, children, ...props }, ref) => (
<div ref={ref} className={cn('p-4 border-t border-secondary-100 bg-secondary-50', className)} {...props}>
{children}
</div>
)
);
CardFooter.displayName = 'CardFooter';
export { Card, CardHeader, CardContent, CardFooter };
export type { CardProps };

View File

@@ -0,0 +1,11 @@
// UI Components - Reusable components for Deal Plus Tech website
// These components follow the project's design system using Tailwind CSS
export { Button } from './Button';
export type { ButtonProps } from './Button';
export { Card, CardHeader, CardContent, CardFooter } from './Card';
export type { CardProps } from './Card';
export { Badge } from './Badge';
export type { BadgeProps } from './Badge';

View File

@@ -9,7 +9,7 @@ export const siteConfig: SiteConfig = {
email: 'info@dealplustech.co.th',
lineId: '@dealplustech',
facebookUrl: 'https://facebook.com/dealplustech',
address: 'กรุงเทพมหานคร ประเทศไทย',
address: 'บริษัท ดีล พลัส เทค จำกัด 9/70 ซอยนครลุง 17 แขวงบางไผ่ เขตบางแค กทม. 10160',
};
export const workHours: WorkHours[] = [