router.push(/products/${product.id}, { shallow: true }); is taking a quite long time... atleast show loading or find any other better solution... keep the performance fast. I'm using nextjs app router.
I want to keep the scroll state of products/page when I navigate to products/[id]/page and then  I navigate back from products/[id]/page to products/page, the products/page scroll state should be saved which is already the case now!
Also improve the performance (without breaking the layout or the design).
I'm using nextjs 15 app router.
I want the loading appears instantly when clicking on the product <Loading /> component which i'm already using supsense in product/[id]/page.jsx
I just want to see something instantly...
ProductList.jsx:
'use client';
import React, {
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useInView } from 'react-intersection-observer';
import dynamic from 'next/dynamic';
import ProductItem from './ProductItem';
import ProductLoading from '@/components/ProductLoading';
import NoProductsFound from '@/components/NoProductsFound';
import Link from 'next/link';
import Image from 'next/image';

const SearchComponent = dynamic(() => import('@/components/SearchComponent'), {
ssr: false,
loading: () => <SearchComponentSkeleton />,
});

export default function ProductList({
initialProducts,
hasMore,
initialPage,
initialQuery,
}) {
const [products, setProducts] = useState(initialProducts);
const [page, setPage] = useState(initialPage);
const [loading, setLoading] = useState(false);
const [noMoreProducts, setNoMoreProducts] = useState(!hasMore);
const [query, setQuery] = useState(initialQuery);
const { ref, inView } = useInView({
threshold: 0,
});
const router = useRouter();
const searchParams = useSearchParams();

const loadMoreProducts = useCallback(() => {
if (loading || noMoreProducts) return;

setLoading(true);
const nextPage = page + 1;
const params = new URLSearchParams(Array.from(searchParams.entries()));
params.set('page', nextPage.toString());
router.replace(/products?${params.toString()}, { scroll: false });
}, [page, loading, noMoreProducts, searchParams, router]);

useEffect(() => {
if (inView && !noMoreProducts) {
loadMoreProducts();
}
}, [inView, loadMoreProducts, noMoreProducts]);

const handleSearch = useCallback(
(newQuery) => {
setQuery(newQuery);
const params = new URLSearchParams(Array.from(searchParams.entries()));
params.set('query', newQuery);
params.set('page', '1');
router.replace(/products?${params.toString()}, { scroll: false });
},
[searchParams, router]
);

useEffect(() => {
setProducts(initialProducts);
setPage(initialPage);
setNoMoreProducts(!hasMore);
setQuery(initialQuery);
setLoading(false);
}, [initialProducts, initialPage, hasMore, initialQuery]);

const memoizedProducts = useMemo(
() =>
products.map((product) => ({
...product,
cheapestPrice: getCheapestPrice(product),
})),
[products]
);

const handleProductClick = useCallback(
(product) => {
router.push(/products/${product.id}, { shallow: true });
},
[router]
);

return (
<>
<header className="flex items-center mb-6">
<Link prefetch={false} className="relative z-20" href={'/'}>
<Image
src="https://storage.naayiq.com/resources/arrow-left.svg"
width={40}
unoptimized={true}
height={40}
alt="left"
priority
/>
</Link>
<h1 className="text-3xl z-10 text-[#181717] left-0 right-0 absolute font-sans text-center font-medium">
Products
</h1>
</header>
<SearchComponent query={query} setQuery={handleSearch} />
{memoizedProducts.length > 0 ? (
<>
<div className="grid grid-cols-2 w-full justify-between gap-4 sm:gap-6 ssm3:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
{memoizedProducts.map((product) => (
<ProductItem
key={product.id}
id={product.id}
name={product.name}
product={product}
handleClick={() => handleProductClick(product)}
price={formatPrice(product.cheapestPrice)}
imageUrl={product.images[0]}
/>
))}
</div>
</>
) : (
<NoProductsFound />
)}
{loading && <ProductLoading />}
{!loading && !noMoreProducts && (
<div ref={ref} style={{ height: '20px' }}></div>
)}
</>
);
}

const getCheapestPrice = (product) => {
const prices = [
product.price && product.price !== '0.00'
? parseFloat(product.price)
: Infinity,
...(product.sizes?.map((size) => parseFloat(size.price)) || []),
...(product.colors?.flatMap((color) => [
parseFloat(color.price),
...(color.sizes?.map((size) => parseFloat(size.price)) || []),
]) || []),
].filter((price) => !isNaN(price) && isFinite(price));

return prices.length > 0 ? Math.min(...prices) : null;
};

const formatPrice = (price) => {
if (price == null || price === Infinity) return 'N/A';
return ${price &gt;= 10000 ? price.toLocaleString() : price} IQD;
};

const SearchComponentSkeleton = () => (
<div className="flex mb-4 items-center space-x-2 animate-pulse">
<div className="h-10 bg-gray-200 rounded-md flex-grow"></div>
<div className="h-10 w-[5.5rem] bg-gray-200 rounded-md"></div>
</div>
);
products/[id]/page.jsx:
import React, { Suspense } from "react";
import ProductDetail from "@/components/ProductDetail";
import Loading from "@/components/Loading";
import { NotificationProvider } from "@/components/NotificationContext";

async function fetchProduct(id) {
const response = await fetch(https://api.naayiq.com/products/${id}, {cache: "force-cache"});
if (!response.ok) {
throw new Error('Failed to fetch product');
}
return response.json();
}

async function ProductContent({ id }) {
const { product } = await fetchProduct(id);

return (
<ProductDetail
product={product}
/>
);
}

export default function ProductDetailsPage({ params }) {
return (
<NotificationProvider>
<Suspense fallback={<Loading />}>
<ProductContent id={params.id} />
</Suspense>
</NotificationProvider>
);
}
ProductDetail.jsx:
'use client'
import React, {useState, useEffect, useCallback} from 'react';
import { ArrowLeft, Minus, Plus } from 'lucide-react';
import Slider from 'react-slick';
import 'slick-carousel/slick/slick.css';
import '@/components/NotificationStyles.css';
import 'slick-carousel/slick/slick-theme.css';
import Image from "next/image";
import { useRouter } from "next/navigation";
import Lightbox from "yet-another-react-lightbox";
import Zoom from "yet-another-react-lightbox/plugins/zoom";
import Fullscreen from "yet-another-react-lightbox/plugins/fullscreen";
import "yet-another-react-lightbox/styles.css";
import { useNotification } from "@/components/NotificationContext";
import Link from "next/link";
import WishlistHeart from "@/components/WishlistHeart";

export default function ProductDetail({ product }) {
    const [selectedColor, setSelectedColor] = useState(null);
    const [selectedSize, setSelectedSize] = useState(null);
    const [quantity, setQuantity] = useState(1);
    const [currentPrice, setCurrentPrice] = useState(product.price);
    const [lightboxOpen, setLightboxOpen] = useState(false);
    const [lightboxIndex, setLightboxIndex] = useState(0);
    const { addNotification } = useNotification();
    const [cartItems, setCartItems] = useState([]);
    const router = useRouter();
    const images = product.images.map(img => img.url || img);

    useEffect(() => {
        window.scrollTo(0, 0);
    }, []);

    const updatePrice = useCallback(() => {
        let price = product.price;
        if (selectedSize && selectedSize.price) {
            price = selectedSize.price;
        } else if (selectedColor && selectedColor.price) {
            price = selectedColor.price;
        }
        setCurrentPrice(parseFloat(price));
    }, [product, selectedColor, selectedSize]);

    useEffect(() => {
        const cart = JSON.parse(localStorage.getItem('cart')) || [];
        setCartItems(cart);

        // Set initial color and size if available
        if (product.has_color && product.colors.length > 0) {
            setSelectedColor(product.colors[0]);
        }
        if (product.has_size && product.sizes.length > 0) {
            setSelectedSize(product.sizes[0]);
        }
        updatePrice();
    }, []);

    useEffect(() => {
        updatePrice();
    }, [selectedColor, selectedSize, updatePrice]);

    const handleColorChange = (color) => {
        setSelectedColor(color);
        if (color.sizes && color.sizes.length > 0) {
            setSelectedSize(color.sizes[0]);
        } else {
            setSelectedSize(null);
        }
    };

    const handleSizeChange = (e) => {
        const sizeId = parseInt(e.target.value);
        const newSize = product.sizes.find(size => size.id === sizeId);
        setSelectedSize(newSize);
    };



    const isInCart = () => {
        return cartItems.some(item =>
            item.product_id === product.id &&
            item.color_id === selectedColor?.id &&
            item.size_id === selectedSize?.id
        );
    };

    const handleAddToCart = () => {
        const cartItem = {
            product_id: product.id,
            color_id: selectedColor?.id,
            size_id: selectedSize?.id,
            qty: quantity
        };

        let updatedCart = [...cartItems];
        const existingItemIndex = updatedCart.findIndex(item =>
            item.product_id === cartItem.product_id &&
            item.color_id === cartItem.color_id &&
            item.size_id === cartItem.size_id
        );

        if (existingItemIndex > -1) {
            updatedCart[existingItemIndex].qty += quantity;
        } else {
            updatedCart.push(cartItem);
        }

        localStorage.setItem('cart', JSON.stringify(updatedCart));
        setCartItems(updatedCart);
        addNotification('success', 'Product Added To Cart');
    };

    const sliderSettings = {
        dots: images.length > 1,
        infinite: false,
        speed: 500,
        slidesToShow: 1,
        slidesToScroll: 1,
        customPaging: function(i) {
            return (
                <div
                    style={{
                        width: '12px',
                        height: '12px',
                        background: i === this.currentSlide ? '#695C5C' : '#D9D9D9',
                        borderRadius: '50%',
                        padding: 0,
                        margin: '0 4px'
                    }}
                />
            );
        },
    };

    const lightboxSlides = images.map(src => ({
        src: `https://storage.naayiq.com/resources/${src}`
    }));

    return (
        <div className="flex overflow-x-hidden font-serif relative z-40 font-medium flex-col -mt-4 -mx-4 bg-white">
            <Slider {...sliderSettings} className="w-full mb-2 h-[55vh]">
                {images.map((image, index) => (
                    <div key={index} className="relative w-full h-[60vh]" onClick={() => { setLightboxIndex(index); setLightboxOpen(true); }}>
                        <Image
                            src={`https://storage.naayiq.com/resources/${image}`}
                            alt={`Product image ${index + 1}`}
                            fill={true}
                            unoptimized={true}
                            className="w-full object-cover cursor-pointer"
                            priority={index === 0}
                        />
                    </div>
                ))}
            </Slider>

            <Lightbox
                open={lightboxOpen}
                close={() => setLightboxOpen(false)}
                index={lightboxIndex}
                slides={lightboxSlides}
                plugins={[Zoom, Fullscreen]}
                carousel={{
                    finite: images.length <= 1,
                    navigationDisabled: images.length <= 1
                }}
                animation={{ zoom: 500 }}
                zoom={{
                    maxZoomPixelRatio: 5,
                    zoomInMultiplier: 2,
                    doubleTapDelay: 300,
                    doubleClickDelay: 300,
                    doubleClickMaxStops: 2,
                    keyboardMoveDistance: 50,
                    wheelZoomDistanceFactor: 100,
                    pinchZoomDistanceFactor: 100,
                    scrollToZoom: true,
                }}
            />

            <button
                className="absolute h-12 rounded-[100%] w-12 bg-white-gradient flex justify-center items-center top-4 left-4 z-10"
                onClick={router.back}
            >
                <ArrowLeft width={30} height={30} strokeWidth={1}/>
            </button>
            <button
                className="h-12 rounded-[100%] w-12 absolute top-4 right-4 z-10 bg-white-gradient flex justify-center items-center"
            >
                <WishlistHeart
                    productId={product.id}
                />
            </button>

            <div className="flex-grow bg-white rounded-t-xl shadow-[0px_-4px_8px_3px_rgba(105,92,92,0.1)] p-6 mt-2 relative z-30">
                <div className="w-9 h-1 bg-black opacity-70 rounded-full mx-auto mb-6"/>
                <h1 className="text-xl font-semibold mb-1 capitalize">{product.name}</h1>

                {product.has_color && product.colors.length > 0 && (
                    <div className="mb-10 mt-6">
                        <h2 className="text-xl font-medium mb-2">Color</h2>
                        <div className="flex space-x-4 overflow-x-auto pb-2">
                            {product.colors.map((color) => (
                                <div key={color.id} className="flex flex-col items-center">
                                    <button
                                        className={`w-20 h-20 rounded-full border-2 ${selectedColor?.id === color.id ? 'border-[#3B5345]' : 'border-[#695C5C] border-opacity-50'} mb-2 overflow-hidden`}
                                        onClick={() => handleColorChange(color)}
                                    >
                                        {color.images && color.images.length > 0 && (
                                            <Image
                                                src={color.images[0]}
                                                alt={color.name}
                                                width={80}
                                                height={80}
                                                className="object-cover"
                                            />
                                        )}
                                    </button>
                                    <span className="text-sm">{color.name}</span>
                                </div>
                            ))}
                        </div>
                    </div>
                )}

                {((product.has_size && product.sizes.length > 0) || (selectedColor && selectedColor.sizes && selectedColor.sizes.length > 0)) && (
                    <div className="mb-6 font-serif">
                        <select
                            value={selectedSize ? selectedSize.id : ''}
                            onChange={handleSizeChange}
                            className="w-32 p-2 border border-[#E5E7EB] rounded-lg font-serif bg-white"
                        >
                            {(selectedColor && selectedColor.sizes ? selectedColor.sizes : product.sizes).map((size) => (
                                <option key={size.id} value={size.id}>
                                    Size: {size.name}
                                </option>
                            ))}
                        </select>
                    </div>
                )}

                <div className="mb-6 pb-28">
                    <h2 className="text-xl font-semibold w-fit mb-2">Description</h2>
                    <div
                        style={{direction: "rtl"}}
                        className="text-xl mr-2 font-normal text-right font-serif"
                        dangerouslySetInnerHTML={{
                            __html: product.description.split('\n').map((item, index) => {
                                if (index === 0) {
                                    return `<p class="text-xl font-semibold mb-2">${item}</p>`;
                                } else if (item.includes(':')) {
                                    return `<p class="text-lg font-semibold mt-4 mb-2">${item}</p>`;
                                } else if (item.trim().startsWith('-')) {
                                    return `<li>${item.trim().substring(1)}</li>`;
                                } else {
                                    return `<p>${item}</p>`;
                                }
                            }).join('').replace(/(<li>.*?<\/li>)+/g, function (match) {
                                return `<ul class="list-disc pl-5 mb-2">${match}</ul>`;
                            })
                        }}
                    />
                </div>

                <footer
                    className="fixed mt-12 border-[#695C5C]/30 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05),0_-2px_4px_-1px_rgba(0,0,0,0.06)] bottom-0 bg-white p-4 right-0 left-0 z-50">
                    <div className="flex justify-between items-center mb-6">
                        <span
                            className="text-xl font-serif font-medium">{(currentPrice * quantity).toFixed(2)} IQD</span>
                        <div className="flex items-center space-x-4">
                            <button
                                onClick={() => setQuantity(Math.max(1, quantity - 1))}
                                className="w-8 h-8 flex items-center justify-center border border-[#E5E7EB] rounded-full"
                            >
                                <Minus className="w-4 h-4 text-[#3B5345]"/>
                            </button>
                            <span className="text-lg font-medium">{quantity}</span>
                            <button
                                onClick={() => setQuantity(quantity + 1)}
                                className="w-8 h-8 flex items-center justify-center border border-[#E5E7EB] rounded-full"
                            >
                                <Plus className="w-4 h-4 text-[#3B5345]"/>
                            </button>
                        </div>
                    </div>

                    {isInCart() ? (
                        <Link
                            href="/cart"
                            className="w-full font-serif bg-[rgba(59,83,69,0.05)] text-[#3B5345] py-3 rounded-lg font-medium text-lg flex items-center justify-center transition duration-300 border border-[#3B5345]"
                        >
                            <svg className="mr-2" width="29" height="28" viewBox="0 0 29 28" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path d="M9.50391 8.94834V7.81668C9.50391 5.19168 11.6156 2.61334 14.2406 2.36834C17.3672 2.06501 20.0039 4.52668 20.0039 7.59501V9.20501" stroke="#3B5345" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round"/>
                                <path d="M11.2542 25.6666H18.2542C22.9442 25.6666 23.7842 23.7883 24.0292 21.5016L24.9042 14.5016C25.2192 11.6549 24.4025 9.33325 19.4209 9.33325H10.0875C5.10586 9.33325 4.28919 11.6549 4.60419 14.5016L5.47919 21.5016C5.72419 23.7883 6.56419 25.6666 11.2542 25.6666Z" stroke="#3B5345" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round"/>
                                <path d="M18.8318 14.0001H18.8423" stroke="#3B5345" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
                                <path d="M10.6638 14.0001H10.6743" stroke="#3B5345" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
                            </svg>
                            Buy Now
                        </Link>
                    ) : (
                        <button
                            onClick={handleAddToCart}
                            className="w-full font-serif bg-[#3B5345] text-white py-3 rounded-lg font-medium text-lg flex items-center justify-center transition duration-300"
                        >
                            <svg className="mr-2" width="29" height="28" viewBox="0 0 29 28" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path d="M9.50391 8.94834V7.81668C9.50391 5.19168 11.6156 2.61334 14.2406 2.36834C17.3672 2.06501 20.0039 4.52668 20.0039 7.59501V9.20501" stroke="white" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round"/>
                                <path d="M11.2542 25.6666H18.2542C22.9442 25.6666 23.7842 23.7883 24.0292 21.5016L24.9042 14.5016C25.2192 11.6549 24.4025 9.33325 19.4209 9.33325H10.0875C5.10586 9.33325 4.28919 11.6549 4.60419 14.5016L5.47919 21.5016C5.72419 23.7883 6.56419 25.6666 11.2542 25.6666Z" stroke="white" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round"/>
                                <path d="M18.8318 14.0001H18.8423" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
                                <path d="M10.6638 14.0001H10.6743" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
                            </svg>
                            Add To Cart
                        </button>
                    )}
                </footer>
            </div>
        </div>
    );
}