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:
@@ -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 */}
|
||||
|
||||
@@ -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
118
src/app/sitemap.ts
Normal 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];
|
||||
}
|
||||
31
src/components/analytics/GoogleAnalytics.tsx
Normal file
31
src/components/analytics/GoogleAnalytics.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
41
src/components/ui/Badge.tsx
Normal file
41
src/components/ui/Badge.tsx
Normal 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 };
|
||||
42
src/components/ui/Button.tsx
Normal file
42
src/components/ui/Button.tsx
Normal 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 };
|
||||
63
src/components/ui/Card.tsx
Normal file
63
src/components/ui/Card.tsx
Normal 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 };
|
||||
11
src/components/ui/index.ts
Normal file
11
src/components/ui/index.ts
Normal 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';
|
||||
@@ -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[] = [
|
||||
|
||||
Reference in New Issue
Block a user