最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - Struggling with React server components and improving LCP speed - Stack Overflow

programmeradmin0浏览0评论

I am struggling to improve my NextJS website performance since the Largest Contentful Paint element is loading too late. I understand why it is loading late, but I am not able to find a solution to render it earlier. LCP load speed

The page in question is the product page of a marketplace. The main React server component (in NextJs) is rendering most of the information, but not the image (the LCP) since I need some interactivity on it and I have separated it into a client component.

Imagine the that the server component is this one:

async function fetchListing(params) {
    try {
        // Bunch of code up here...
        let listing = await Listing.findOne({ _id: params.id });
        return listing;
    } catch (err) {
        console.log(err);
        return null;
    }
}

const ProductPage = async ({ params }) => {
    let listing = await fetchListing(params); // Reusing fetchListing here

    if (!listing) return <NotFoundSection />;

    listing = JSON.parse(JSON.stringify(listing));

    return (
        <>
            {/* Bunch of code up here.. */}

            <div className={classes.maxWidth}>
                    <div className={classes.lastLink}>{listing.title}</div>

                <div className={classes.topSection}>
                        <Carousel listing={listing} />
                    {/* Bunch more code down here.. */}
                </div>
            </div>
        </>
    );
};
export default ProductPage;

And the carousel component looks like this:

'use client';

import { useEffect, useState } from 'react';
import Image from 'next/image';
import classes from './Carousel.module.css';
import {
    ChevronDown,
    ChevronLeft,
    ChevronRight,
    ChevronUp,
} from 'react-feather';

const Carousel = ({ listing }) => {
    const [previewSet, setPreviewSet] = useState([]);
    const [currentPage, setCurrentPage] = useState(0);
    const [current, setCurrent] = useState(0);
    const [idx, setIdx] = useState(0);
    const [currentImg, setCurrentImg] = useState();

    useEffect(() => {
        let preview = [];
        let i = 0;
        if (currentPage === 0) {
            preview = listing.images.slice(0, 6);
            i = 0;
        } else if (currentPage === 1) {
            preview = listing.images.slice(6, 12);
            i = 6;
        } else if (currentPage === 2) {
            preview = listing.images.slice(12, 18);
            i = 12;
        } else if (currentPage === 3) {
            preview = listing.images.slice(18, 24);
            i = 18;
        }
        setIdx(i);
        setPreviewSet(preview);
    }, [currentPage, listing]);

    const nextHandler = () => {
        if (current === listing.images.length - 1) {
            setCurrent(0);
            setCurrentPage(0);
            setCurrentImg(listing.images[0]);
        } else if (
            (current === 5 && currentPage === 0) ||
            (current === 11 && currentPage === 1) ||
            (current === 17 && currentPage === 2) ||
            (current === 23 && currentPage === 3)
        ) {
            setCurrentPage(currentPage + 1);
            setCurrent(current + 1);
        } else setCurrent(current + 1);
    };

    const previousHandler = () => {
        if (
            (current === 6 && currentPage === 1) ||
            (current === 12 && currentPage === 2) ||
            (current === 18 && currentPage === 3) ||
            (current === 24 && currentPage === 4)
        ) {
            setCurrentPage(currentPage - 1);
            setCurrent(current - 1);
        } else {
            setCurrent(current - 1);
        }
    };

    const handleClick = (index) => {
        setCurrent(index + idx);
    };

    useEffect(() => {
        setCurrentImg(listing.images[current]);
    }, [current, listing]);

    const nextPage = () => {
        setCurrentPage(currentPage + 1);
    };

    const previousPage = () => {
        setCurrentPage(currentPage - 1);
    };

    return (
        <>
            <div className={classes.carouselContainer}>
                <div className={classes.wrapper}>
                    <div className={classes.wrapperContainer}>
                        {currentPage !== 0 && (
                            <div className={classes.scroll} onClick={() => previousPage()}>
                                <ChevronUp className={classes.arrow} />
                            </div>
                        )}
                    </div>

                    <ul className={classes.imagePreview}>
                        {previewSet?.map((image, idx) => (
                            <li>
                                <Image
                                    className={
                                        currentImg === image ? classes.image : classes.nonActive
                                    }
                                    src={
                                        image?.includes('imagekit.io')
                                            ? `${image}?tr=w-80,h-80,q-75`
                                            : image
                                    }
                                    effect="blur"
                                    alt={`${listing.title} - image ${idx}`}
                                    width={80}
                                    height={80}
                                    onMouseEnter={() => handleClick(idx)}
                                />
                            </li>
                        ))}
                    </ul>
                    <div className={classes.wrapperContainer}>
                        {previewSet.length >= 6 && listing.images.length > 6 && (
                            <button className={classes.scroll} onClick={() => nextPage()}>
                                <ChevronDown className={classes.arrow} />
                            </button>
                        )}
                    </div>
                </div>

                <div className={classes.largeImagePreview}>
                    <div className={classes.next} onClick={() => nextHandler()}>
                        <ChevronRight className={classes.arrow} />
                    </div>
                    {current !== 0 && (
                        <div className={classes.previous} onClick={() => previousHandler()}>
                            <ChevronLeft className={classes.arrow} />
                        </div>
                    )}
                    <div className={classes.wrapp}>
                        <Image
                            priority={true}
                            src={`${listing.images[current]}?tr=w-876,h-1134,c-at_max_enlarge`}
                            className={
                                listing.status !== 'active'
                                    ? classes.imageSold
                                    : classes.imageLarge
                            }
                            height={633}
                            width={474}
                            alt={`${listing.title} - image ${current}`}
                        />
                    </div>
                </div>
            </div>
        </>
    );
};

export default Carousel;

The Carousel component will render to look something like this Carousel screenshot

Now, that <Image /> component with priority set to true is going to be my LCP.

I understand that my loading time for the LCP is long because this <Image /> component is loading in the client, much later than then the rest of the server rendered content which I did not paste in the ProductPage component here.

I know this also because I ran a test where I edited the Carousel component to remove all interactivity and make it a Server component, this improved the LCP loading speed dramatically.

My question is, how can I work around this? How can I render the Carousel component in the server, and then hydrate it in the client so that it can maintain its interactivity (scrolling images left and right)

发布评论

评论列表(0)

  1. 暂无评论