summaryrefslogtreecommitdiff
path: root/src-migrate/pages/shop
diff options
context:
space:
mode:
authorMiqdad <ahmadmiqdad27@gmail.com>2025-10-28 12:26:03 +0700
committerMiqdad <ahmadmiqdad27@gmail.com>2025-10-28 12:26:03 +0700
commit7f71d52e2e6e6e8ffb5ea2837be84c800d04ef95 (patch)
treeb7f687b5f3034adff037315513f2f216fc87328d /src-migrate/pages/shop
parentb3367a2d1882e0da9a366e1dfe07e9c9851989e3 (diff)
<Miqdad> canonical product detail
Diffstat (limited to 'src-migrate/pages/shop')
-rw-r--r--src-migrate/pages/shop/product/[slug].tsx120
1 files changed, 83 insertions, 37 deletions
diff --git a/src-migrate/pages/shop/product/[slug].tsx b/src-migrate/pages/shop/product/[slug].tsx
index 90658544..058e4832 100644
--- a/src-migrate/pages/shop/product/[slug].tsx
+++ b/src-migrate/pages/shop/product/[slug].tsx
@@ -1,71 +1,118 @@
-import { GetServerSideProps, NextPage } from 'next'
-import React, { useEffect } from 'react'
-import dynamic from 'next/dynamic'
-import cookie from 'cookie'
-
-import { getProductById } from '~/services/product'
-import { getIdFromSlug } from '~/libs/slug'
-import { IProductDetail } from '~/types/product'
-
-import { Seo } from '~/components/seo'
-import { useRouter } from 'next/router'
-import { useProductContext } from '@/contexts/ProductContext'
-
-const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout'), { ssr: false })
-const ProductDetail = dynamic(() => import('~/modules/product-detail'), { ssr: false })
+import { GetServerSideProps, NextPage } from 'next';
+import React, { useEffect } from 'react';
+import dynamic from 'next/dynamic';
+import cookie from 'cookie';
+
+import { getProductById } from '~/services/product';
+import { createSlug, getIdFromSlug } from '~/libs/slug'; // <-- tambahin createSlug
+import { IProductDetail } from '~/types/product';
+
+import { Seo } from '~/components/seo';
+import { useRouter } from 'next/router';
+import { useProductContext } from '@/contexts/ProductContext';
+
+const BasicLayout = dynamic(
+ () => import('@/core/components/layouts/BasicLayout'),
+ { ssr: false }
+);
+const ProductDetail = dynamic(() => import('~/modules/product-detail'), {
+ ssr: false,
+});
type PageProps = {
- product: IProductDetail
-}
+ product: IProductDetail;
+ canonicalPath: string;
+};
-export const getServerSideProps: GetServerSideProps<PageProps & { canonicalPath: string }> = async (context) => {
+export const getServerSideProps: GetServerSideProps<PageProps> = async (
+ context
+) => {
const { slug } = context.query;
+
+ // ambil cookie pricelist tier
const cookieString = context.req.headers.cookie;
const cookies = cookieString ? cookie.parse(cookieString) : {};
const auth = cookies?.auth ? JSON.parse(cookies.auth) : {};
const tier = auth?.pricelist || '';
+ // ambil ID produk dari slug URL
const productId = getIdFromSlug(slug as string);
+
+ // fetch data produk dari backend lo
const product = await getProductById(productId, tier);
- // ❌ produk tidak ada → 404
+ // hard guard: produk gak ada -> 404
if (!product) return { notFound: true };
- // ❌ tidak ada variants atau tidak ada yang harga > 0 → 404
- const hasValidVariant = Array.isArray(product.variants)
- && product.variants.some(v => (v?.price?.price ?? 0) > 0);
+ // guard: gak ada varian harga valid -> 404
+ const hasValidVariant =
+ Array.isArray(product.variants) &&
+ product.variants.some((v) => (v?.price?.price ?? 0) > 0);
+
if (!hasValidVariant) return { notFound: true };
- // Canonical path aman untuk SSR (hindari router.asPath di server)
- const canonicalPath = context.resolvedUrl || `/product/${slug}`;
+ // bikin canonical path yang BERSIH dan KONSISTEN dari data produk,
+ // bukan dari URL request user (jadi gak ikut ?utm_source, ?ref=, dsb)
+ const canonicalPath = createSlug(
+ '/shop/product/', // ganti ini sesuai prefix route produk lo yang SEBENARNYA
+ product?.name || '',
+ product?.id,
+ false // false = jangan include host di sini
+ );
- return { props: { product, canonicalPath } };
+ return {
+ props: {
+ product,
+ canonicalPath,
+ },
+ };
};
-
-const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST
-
-const ProductDetailPage: NextPage<PageProps & { canonicalPath: string }> = ({ product, canonicalPath }) => {
+const ProductDetailPage: NextPage<PageProps> = ({ product, canonicalPath }) => {
const router = useRouter();
const { setProduct } = useProductContext();
- useEffect(() => { if (product) setProduct(product); }, [product, setProduct]);
+ // taruh product di context global lo
+ useEffect(() => {
+ if (product) setProduct(product);
+ }, [product, setProduct]);
+
+ // rapihin origin biar gak double slash
+ const origin = (process.env.NEXT_PUBLIC_SELF_HOST || '').replace(/\/+$/, '');
+ const pathClean = canonicalPath.startsWith('/')
+ ? canonicalPath
+ : `/${canonicalPath}`;
+ const url = origin + pathClean;
- const origin = process.env.NEXT_PUBLIC_SELF_HOST || '';
- const url = origin + (canonicalPath?.startsWith('/') ? canonicalPath : `/${canonicalPath}`);
+ // optional: pastiin OG image absolute URL
+ const ogImageUrl = product?.image?.startsWith('http')
+ ? product.image
+ : origin + product?.image;
return (
<BasicLayout>
<Seo
title={`${product.name} - Indoteknik.com`}
description='Temukan pilihan produk B2B Industri &amp; Alat Teknik untuk Perusahaan, UMKM &amp; Pemerintah dengan lengkap, mudah dan transparan.'
+ canonical={url} // <- ini diprioritaskan sama komponen Seo
openGraph={{
url,
- images: [{ url: product?.image, width: 800, height: 800, alt: product?.name }],
+ images: [
+ {
+ url: ogImageUrl,
+ width: 800,
+ height: 800,
+ alt: product?.name,
+ },
+ ],
type: 'product',
}}
- additionalMetaTags={[{ name: 'keywords', content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}` }]}
- canonical={url}
+ additionalMetaTags={[
+ {
+ name: 'keywords',
+ content: `${product?.name}, Harga ${product?.name}, Beli ${product?.name}, Spesifikasi ${product?.name}`,
+ },
+ ]}
/>
<div className='md:container pt-4 md:pt-6'>
@@ -75,5 +122,4 @@ const ProductDetailPage: NextPage<PageProps & { canonicalPath: string }> = ({ pr
);
};
-
-export default ProductDetailPage \ No newline at end of file
+export default ProductDetailPage;