You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GatsbyJS v4.12.1直接访问URL时页面重复渲染问题求助

问题

使用GatsbyJS v4.12.1搭建的网站中,产品搜索页通过页面模板的page context传递产品数据。从菜单导航进入页面时运行正常,但直接通过URL访问该页面时,组件会出现两次渲染:先无产品显示,之后才加载出产品内容。

相关代码:

import * as React from 'react'
import { useState } from 'react'
import { Divider, Grid, Pagination, sliderClasses, Typography } from '@mui/material'
import { makeStyles } from '@mui/styles'
import FilterComponent from './sidebar-filter'
import ProductCard from './product-card'
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';

const getFilterModel = (products) => {
    let brands = []
    let types = []
    let ages = []
    let breeds = []
    let features = []
    let petTypes=[]
    products.forEach(product => {
        brands.push(product?.brand?.brandName)
        product.type?.forEach(type => types.push(type))
        product.ageGroup?.forEach(age => ages.push(age))
        product.breeds?.forEach(breed => breeds.push(breed))
        product.special_care?.forEach(feature => features.push(feature.name))
        petTypes.push(product.for_cats?'cats':'dogs')
    });
    
    return { brands: [...new Set(brands)], types: [...new Set(types)], ages: [...new Set(ages)].sort(), breeds: [...new Set(breeds)], features: [...new Set(features)], petTypes: [...new Set(petTypes)] };
}

const filterPrdcts = (products, filter) => {
    let petTypes = []
    if (filter?.petTypes) {
        if (filter.petTypes.includes('cats')) {
            petTypes.push(true)
        }
        if (filter.petTypes.includes('dogs')) {
            petTypes.push(false)
        }
    }
    const filteredByBrands = (filter.brands.length > 0) ? products.filter(product => filter.brands.includes(product.brand.brandName)) : [...products]
    const filteredByTypes = (filter.types.length > 0) ? filteredByBrands.filter(product => filter.types.some(r => product.type.includes(r))) : [...filteredByBrands]
    const filteredByPetTypes = (petTypes.length > 0) ? filteredByTypes.filter(product => petTypes.includes(product.for_cats)) : [...filteredByTypes]
    const filteredByBreeds = (filter.breeds.length > 0) ? filteredByPetTypes.filter(product => filter.breeds.some(r => product.breeds?.includes(r))) :[...filteredByPetTypes]
    const filteredByAgeGroups = (filter.ages.length > 0) ? filteredByBreeds.filter(product => filter.ages.some(r => product.ageGroup.includes(r))) : [...filteredByBreeds]
    
    const filteredByFeature = (filter.features?.length > 0) ? filteredByAgeGroups.filter(product => filter.features.some(r => (product.special_care?.filter(feature => (feature.name === r)).length > 0))) : [...filteredByAgeGroups]

    return (filteredByFeature)
}

const useStyles = makeStyles(theme => ({
    wrapper: {
        display: 'flex',
        flexDirection: 'row',
    },
    productListWrapper: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        gap: '2rem'
    },
    hiddenOnMobile: {
        [theme.breakpoints.down('md')]: {
            display: 'none'
        }
    },
    hiddenOnDesktop: {
        [theme.breakpoints.up('md')]: {
            display: 'none'
        }
    }
}))

const ProductList = ({ products, checked, handleChange, flipPage}) => {
    const [filteredProducts, setFilteredProducts] = useState(products)
    const classes = useStyles()
    const filterModel = getFilterModel(products)
    const itemsPerPage = 9

    React.useEffect(() => {
        const filtered = filterPrdcts(products, checked)
        setFilteredProducts(filtered)
    }, [checked, products])

    React.useEffect(()=> {
        return () => console.log('unmounting Productlist')
    },[])
  
    return (
        <Grid className={classes.wrapper} container >
            <Grid item xs={3} sx={{ paddingTop: '10px' }} className={classes.hiddenOnMobile}>
                <FilterComponent options={filterModel} handleChange={handleChange} checked={checked} />
            </Grid>
            <Grid item xs={12} className={classes.hiddenOnDesktop}>
                <FilterAccordion options={filterModel} handleChange={handleChange} checked={checked} />
            </Grid>
            <Grid item xs={12} md={9} sx={{ paddingTop: '10px' }}>
                <div className={classes.productListWrapper}>
                    {filteredProducts?.slice((checked.page-1)*itemsPerPage,checked.page*itemsPerPage).map((product, index) => {
                        return (
                            <React.Fragment key={index}>
                                <ProductCard product={product} />
                                <Divider variant="fullWidth" className={classes.hiddenOnDesktop} sx={{width:'100%'}}/>
                            </React.Fragment>
                        )
                    })}
                </div>
                <Grid><Pagination count={(filteredProducts.length-filteredProducts.length%itemsPerPage)/itemsPerPage+1} onChange={flipPage} page={parseInt(checked.page)}/></Grid>
            </Grid>
        </Grid>
    )
}

const FilterAccordion = ({ options, handleChange, checked }) => {
    return (
        <Accordion >
            <AccordionSummary
                expandIcon={<ExpandMoreIcon />}
                aria-controls="panel1a-content"
                id="panel1a-header"
            >
                <Typography>Ürün Filtrele</Typography>
            </AccordionSummary>
            <AccordionDetails>
                <FilterComponent options={options} handleChange={handleChange} checked={checked} />
            </AccordionDetails>
        </Accordion>
    )
}

export default ProductList
解决方案

原因分析

直接URL访问时,Gatsby的SSR/SSG阶段可能未正确传递page context中的products数据,导致组件首次渲染时products为空;后续客户端hydration阶段拿到完整数据后再次渲染,从而出现“先空白后加载”的现象。另外,filteredProducts的初始化逻辑未考虑过滤规则,也会加剧这种渲染差异。

具体修复步骤

  1. 优化状态初始化逻辑
    将filteredProducts的初始值直接设置为过滤后的结果,避免初始状态与后续过滤结果不一致:

    const [filteredProducts, setFilteredProducts] = useState(() => filterPrdcts(products || [], checked))
    

    使用函数式初始化可以避免每次渲染都执行过滤逻辑,提升性能。

  2. 添加加载状态兜底
    在组件开头判断products是否存在或为空,显示加载提示避免空白:

    if (!products?.length) {
      return <div style={{padding: '2rem', textAlign: 'center'}}>Ürünler yükleniyor...</div>
    }
    
  3. 确保page context数据在SSR/SSG时正确传递
    检查页面模板的createPage逻辑,确保在构建阶段就获取到完整的产品数据并传入page context,而不是仅在客户端异步获取。例如:

    // gatsby-node.js中的createPage部分
    createPage({
      path: '/urunler',
      component: require.resolve('./src/templates/product-list.js'),
      context: {
        products: await fetchProductsData(), // 确保这里同步获取数据
      },
    })
    
  4. 优化useEffect依赖与逻辑
    确保useEffect在products或checked变化时正确更新过滤结果,同时避免不必要的重渲染:

    React.useEffect(() => {
      if (!products) return
      const filtered = filterPrdcts(products, checked)
      setFilteredProducts(filtered)
    }, [checked, products])
    

内容的提问来源于stack exchange,提问作者Ergun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 23:07:10