perf: optimize font payloads, enable responsive image optimization for uploads, and set high priority on top hero/product images
Some checks failed
Deploy Canina / deploy (push) Successful in 2m14s
E2E Playwright Tests / Run Full E2E & Security Suites (push) Failing after 6s

This commit is contained in:
parsa aghaei 2026-09-02 15:54:12 +03:30
parent 00839017ff
commit 3aaea0062b
6 changed files with 30 additions and 26 deletions

View File

@ -15,6 +15,13 @@ function getCandidateUrls(filePath: string): string[] {
set.add(`${base}/uploads/${filePath}`);
}
// Production backend port and domains
set.add(`http://127.0.0.1:4400/uploads/${filePath}`);
set.add(`http://localhost:4400/uploads/${filePath}`);
set.add(`http://127.0.0.1:4000/uploads/${filePath}`);
set.add(`http://localhost:4000/uploads/${filePath}`);
set.add(`https://api.canina.ir/uploads/${filePath}`);
set.add(`http://backend_prod:3000/uploads/${filePath}`);
set.add(`http://canino_backend_prod:3000/uploads/${filePath}`);
set.add(`http://backend:3000/uploads/${filePath}`);

View File

@ -15,6 +15,13 @@ function getCandidateUrls(filePath: string): string[] {
set.add(`${base}/uploads/${filePath}`);
}
// Production backend port and domains
set.add(`http://127.0.0.1:4400/uploads/${filePath}`);
set.add(`http://localhost:4400/uploads/${filePath}`);
set.add(`http://127.0.0.1:4000/uploads/${filePath}`);
set.add(`http://localhost:4000/uploads/${filePath}`);
set.add(`https://api.canina.ir/uploads/${filePath}`);
// Docker internal service aliases
set.add(`http://backend_prod:3000/uploads/${filePath}`);
set.add(`http://canino_backend_prod:3000/uploads/${filePath}`);

View File

@ -13,9 +13,9 @@ import { useSettingsStore } from "../lib/store/settingsStore";
import { useCartStore } from "../lib/store/cartStore";
import { toast } from "sonner";
function ProductCard({ product }: { product: Product }) {
function ProductCard({ product, priority = false }: { product: Product; priority?: boolean }) {
const router = useRouter();
const getText = useSettingsStore((state) => state.getText);
const { getText, isInitialized } = useSettingsStore();
const { getActivePet } = usePetStore();
const activePet = getActivePet();
const { addItem } = useCartStore();
@ -36,17 +36,16 @@ function ProductCard({ product }: { product: Product }) {
return { type: "neutral", text: `مناسب برای ${activePet.name || 'پت شما'}` };
}, [product, activePet]);
const isCatalogOnly = getText('CATALOG_ONLY_MODE', 'false') === 'true';
const showPrices = !isCatalogOnly || getText('CATALOG_SHOW_PRICES', 'true') === 'true';
const allowCart = !isCatalogOnly || getText('CATALOG_ALLOW_CART', 'false') === 'true';
const showPreorderBtn = isCatalogOnly && getText('CATALOG_PREORDER_BTN', 'false') === 'true';
const isCatalogOnly = isInitialized ? getText('CATALOG_ONLY_MODE', 'false') === 'true' : false;
const showPreorderBtn = isCatalogOnly && (isInitialized ? getText('CATALOG_PREORDER_BTN', 'false') === 'true' : false);
const allowCart = !isCatalogOnly && (isInitialized ? getText('allow_cart_purchase', 'true') === 'true' : true);
const showPrices = !isCatalogOnly || (isInitialized ? getText('show_product_prices', 'true') === 'true' : true);
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
whileHover={{ y: -4 }}
transition={{ duration: 0.3, ease: "easeOut" }}
onClick={() => router.push(`/shop/${product.slug || product.id}`)}
className="group bg-white/90 backdrop-blur-md rounded-2xl sm:rounded-3xl border border-medical-gray-200/80 overflow-hidden hover:shadow-xl hover:shadow-canina-blue/10 hover:border-canina-blue/30 transition-all duration-300 flex flex-col cursor-pointer relative"
@ -72,6 +71,7 @@ function ProductCard({ product }: { product: Product }) {
<SafeImage
src={product.image}
alt={nameFa}
priority={priority}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw)"
className="w-full h-full group-hover:scale-105 transition-transform duration-500"
imgClassName="object-contain w-full h-full max-h-[160px] sm:max-h-[200px] mx-auto"
@ -193,7 +193,7 @@ export default function FeaturedProducts() {
{isLoading ? (
Array.from({ length: 4 }).map((_, i) => <ProductCardSkeleton key={i} />)
) : products.length > 0 ? (
products.map((p) => <ProductCard key={p.id} product={p} />)
products.map((p, i) => <ProductCard key={p.id} product={p} priority={i < 2} />)
) : (
<div className="col-span-full py-16 px-8 flex flex-col items-center justify-center text-center bg-white rounded-[2.5rem] border border-medical-gray-200 shadow-xl max-w-lg mx-auto">
<AlertCircle className="w-12 h-12 text-canina-teal mb-4" />

View File

@ -198,7 +198,7 @@ export default function Hero({ banners = [], onShopNavigate }: { banners?: Banne
alt={banner.title || "Canina Pharma Germany"}
fill
priority={idx === 0}
unoptimized={true}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 600px"
className="object-cover"
/>
</motion.div>
@ -210,7 +210,7 @@ export default function Hero({ banners = [], onShopNavigate }: { banners?: Banne
alt={activeBanner?.title || "Canina Pharma Germany"}
fill
priority
unoptimized={true}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 600px"
className="object-cover"
/>
</div>

View File

@ -104,12 +104,7 @@ export default function SafeImage({
unoptimized={
isBlobOrData ||
!isAllowedHost ||
(typeof src === 'string' && (
src.startsWith('/api/uploads/') ||
src.startsWith('/api/media/') ||
src.startsWith('/uploads/') ||
src.toLowerCase().endsWith('.svg')
))
(typeof src === 'string' && src.toLowerCase().endsWith('.svg'))
}
className={cn(
"transition-all duration-500 w-full h-full object-contain",

View File

@ -1,3 +1,4 @@
import { Lalezar } from 'next/font/google';
import localFont from 'next/font/local';
export const vazirmatn = localFont({
@ -13,16 +14,10 @@ export const vazirmatn = localFont({
preload: true,
});
export const lalezar = localFont({
src: [
{
path: '../public/fonts/Lalezar-Font-fontyab.com_/Lalezar-Font/TTF/Lalezar-Regular.ttf',
weight: '400',
style: 'normal',
},
],
export const lalezar = Lalezar({
weight: '400',
subsets: ['arabic'],
variable: '--font-lalezar',
display: 'swap',
fallback: ['cursive', 'sans-serif'],
preload: false,
});