feat: dynamic SEO metadata and JSON-LD for products
This commit is contained in:
parent
bf1b11d6a9
commit
700f93e418
@ -1,6 +1,78 @@
|
||||
import ProductPage from "../../../components/ProductPage";
|
||||
import { productService } from "../../../lib/services/productService";
|
||||
import type { Metadata, ResolvingMetadata } from 'next';
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata> {
|
||||
const resolvedParams = await params;
|
||||
const product = await productService.getProductById(resolvedParams.id);
|
||||
|
||||
if (!product) {
|
||||
return {
|
||||
title: 'محصول یافت نشد',
|
||||
};
|
||||
}
|
||||
|
||||
// Get raw data for custom fields if we added them (like metaTitle)
|
||||
// Since we don't have local access to raw DB object without modifying productService,
|
||||
// we can just use the mapped product fields for now.
|
||||
const title = (product as any).metaTitle || product.name;
|
||||
const description = (product as any).metaDescription || product.description;
|
||||
const keywords = (product as any).keywords ? (product as any).keywords.split(',') : product.main_ingredients;
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
keywords,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
images: [product.image],
|
||||
},
|
||||
alternates: {
|
||||
canonical: (product as any).canonicalUrl || `/shop/${product.id}`,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ShopProductPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const resolvedParams = await params;
|
||||
return <ProductPage productId={resolvedParams.id} />;
|
||||
const product = await productService.getProductById(resolvedParams.id);
|
||||
|
||||
if (!product) {
|
||||
return <ProductPage productId={resolvedParams.id} />;
|
||||
}
|
||||
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Product',
|
||||
name: product.name,
|
||||
image: product.image,
|
||||
description: (product as any).metaDescription || product.description,
|
||||
sku: product.artNo,
|
||||
brand: {
|
||||
'@type': 'Brand',
|
||||
name: 'Canina'
|
||||
},
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
url: `https://canina-iran.com/shop/${product.id}`,
|
||||
priceCurrency: 'IRR',
|
||||
price: product.priceValue * 10, // Assuming priceValue is in Toman, converting to Rial
|
||||
availability: 'https://schema.org/InStock',
|
||||
itemCondition: 'https://schema.org/NewCondition'
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
<ProductPage productId={resolvedParams.id} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const baseURL = typeof window !== 'undefined' ? '/api' : (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api');
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: '/api',
|
||||
baseURL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -11,7 +13,10 @@ const api = axios.create({
|
||||
// Interceptor to add auth token in the future
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
let token = null;
|
||||
if (typeof window !== 'undefined') {
|
||||
token = localStorage.getItem('accessToken');
|
||||
}
|
||||
if (token && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user