使用可加载库的 SSR React 应用程序,在主路由服务器上间歇性地抛出“无法将未定义或空值转换为对象”错误

问题描述

我有一个使用 React & Redux 构建的(服务器端渲染)SSR 应用程序。我也使用可加载库。该应用程序托管在 Heroku 上并使用 Cloudflare 缓存。

应用程序在 Heroku 上间歇性地以 500 错误结束,我无法找到错误的根本原因。以下是错误信息

loadable-components: Failed to synchronously load component,which expected to be available {
    fileName: 592,chunkName: 'home-route',error: 'Cannot convert undefined or null to object'
}

TypeError: Cannot convert undefined or null to object
at getPrototypeOf (<anonymous>)
at hoistNonReactStatics (/app/node_modules/@loadable/component/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js:70:32)
at resolve (/app/node_modules/@loadable/component/dist/loadable.cjs.js:128:7)
at InnerLoadable.loadSync (/app/node_modules/@loadable/component/dist/loadable.cjs.js:279:24)
at new InnerLoadable (/app/node_modules/@loadable/component/dist/loadable.cjs.js:173:17)
at d (/app/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:320)
at $a (/app/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:39:16)
at a.b.render (/app/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:44:476)
at a.b.read (/app/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:44:18)
at Object.renderToString (/app/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:54:364)
at /app/static/dist/server/server.js:79236:42
at runMicrotasks (<anonymous>)
at processticksAndRejections (internal/process/task_queues.js:97:5)

由于这种情况是间歇性发生的,我无法找到发生这种情况的根本原因。以下是发生错误的 Home 组件的详细信息

src/routes/HomeRoute.js

// Libs
import { asyncConnectWithModifications } from 'decorators/asyncConnectWithModifications'
import { compose } from 'redux'
import { connect } from 'react-redux'
import path from 'ramda/src/path'
import loadable from '@loadable/component'
// Actions
import { getTotalCampers } from 'myredux/modules/search'
import { getPagesList } from 'myredux/modules/content'
// Constants
import defaultCoords from 'routes/Search/defaultCoords'
// Components
const Home = loadable(() => import(/* webpackChunkName: "home-route" */ './Home'))

export default compose(
  asyncConnectWithModifications({
    getPromise: ({ store: { dispatch,getState } }) => {
      const state = getState()
      const totalCampers = path(['search','totalCampeRSSearchResult','Pagination','Total'],state)
      const totalCampeRSSearch = path(['search','searchResult',state)
      const promises = []
      if (!totalCampers || (totalCampeRSSearch > totalCampers)) {
        // Set default coords
        const { centerLat,centerLng } = defaultCoords[state.app.country.code] || {}
        promises.push(dispatch(getTotalCampers({ center_lat: centerLat,center_lng: centerLng })))
      }
      promises.push(dispatch(
        getPagesList('posts'),))

      return Promise.all(promises)
    },}),connect(state => ({
    locale: state.app.locale,})),)(Home)

src/routes/Home.js

...
  render() {
    const { app,env,search,content,locale,translate,formatNumber,lastDraftCamperId,userId } = this.props
    const { bannerCamperClosed } = this.state
    const totalCampers = path(['totalCampeRSSearchResult',search)
    const formattedTotalCampers = formatNumber(totalCampers)

    // Canonical for home page is current domain
    const canonical = `https://${app.domain.name}/`

    const Meta = {
      title: translate({ id: 'home.pageTitle' }),description: translate({ id: 'home.pageDescription' }),link: [
        { rel: 'canonical',href: canonical },{ rel: 'alternate',href: 'https://paulcamper.de/',hrefLang: 'de-DE' },href: 'https://paulcamper.at/',hrefLang: 'de-AT' },// { rel: 'alternate',href: 'https://paulcamper.co.uk/',hrefLang: 'en-GB' },// UK domain is disabled for Now [.co.uk uncomment]
        // { rel: 'alternate',href: 'https://paulcamper.es/',hrefLang: 'es-ES' },// ES domain is disabled for Now [.es uncomment]
        // { rel: 'alternate',href: 'https://paulcamper.it/',hrefLang: 'it-IT' },// Italian domain is disabled for Now [.it uncomment]
        { rel: 'alternate',href: 'https://paulcamper.nl/',hrefLang: 'nl-NL' },],}

    const numberOfMonths = app.breakpoint === 'xs' ? 1 : 2
    const { DE } = LOCALE_LANGUAGES
    const { ES,FR,IT } = LOCALE_CODES
    const doHideHomeCities = [ES,IT].includes(locale.code)

    return (
      <nestedStatus maxage={DAY}>
        <div className={styles.container}>
          <DocumentMeta {...Meta}/>
          <BlockedLenderBanner/>
          <BodyClassName className="page-home"/>
          {!bannerCamperClosed && lastDraftCamperId &&
          <HomeBannerCamper
            camperId={lastDraftCamperId}
            userId={userId}
            onClose={this.handleBannerCamperClose}
          />}
          <div className={styles.head} style={{ backgroundImage: `url(${this.bgImage})` }}>
            <div className={styles.headContent}>
              <div className={styles.header}>
                <Header
                  theme="light"
                  pageName="home"
                  isHomePage
                  isLenderButtonActive
                  isSearchButtonActive={false}
                />
              </div>
              <Grid>
                <Row>
                  <Col xs={12}>
                    <div className={styles.title}>
                      <Row>
                        <Col xs={12} md={10} mdOffset={1} lg={8} lgOffset={2}>
                          <h1 className={styles.headTitle}>
                            <FormattedMessage id="home.v2.title.xp"/>
                          </h1>
                          {app.locale.language === 'nl' &&
                          <h2 className={styles.headSubTitle}>
                            <FormattedMessage id="home.subTitle"/>
                          </h2>}
                          <TrustBoxNew
                            withoutReviews
                            white
                            small
                          />
                        </Col>
                      </Row>
                    </div>
                  </Col>
                </Row>
              </Grid>
              <Grid>
                <Row>
                  <Col xs={12}>
                    <div className={styles.search}>
                      <SearchFormHome
                        locale={locale}
                        numberOfMonths={numberOfMonths}
                        filters={search.filters}
                        onApply={this.handleSearchFormSubmit}
                        onLocationChange={this.handleLocationChange}
                        onFiltersChange={this.props.setFilters}
                        onSubmit={this.handleSearchFormSubmit}
                        withAutoFocus={false}
                        submitLocationOnBlur
                      />
                    </div>
                    <div className={styles.publicRequest}>
                      <FormattedHTMLMessage id="home.publicRequests" values={{ total: formattedTotalCampers }}/>
                      {' '}
                      <Link to="/add-public-request/" onClick={this.handlePublickRequest}>
                        <FormattedMessage id="home.publicRequests.create"/>
                        <i className={styles.publicRequestIcon}>
                          <Icon src={require('icons/icon-arrow-left.svg')}/>
                        </i>
                      </Link>
                    </div>
                  </Col>
                </Row>
              </Grid>
              {/** app.locale.language === 'de' &&
               <div className={styles.firstPlaceBanner}>
               <FirstPlaceBanner/>
               </div> **/}
            </div>
          </div>
          <SearchWizardTest component={(
            <SearchWizardCta locale={locale} onClick={this.handleSearchWizardClick}/>
          )}
          />
          <div className={styles.trust}>
            <HomeTrust app={app}/>
          </div>
          <PromiseBlock/>
          <AwarenessBlock>
            {locale.code === LOCALE_CODES.DE &&
            <AwarenessBlockItem
              buttonText="Ja,gerne"
              onButtonClick={this.handleRecruitingAwareClick}
              preset={2}
              text="Wir führen regelmäßig Umfragen zur Website,zu Camping und Reisethemen durch. Bist du dabei?"
              title="Lass uns PaulCamper noch besser machen"
              trackName="test-user-recruiting"
              trackPage="home"
            />}
            <AwarenessBlockItem
              buttonText={translate({ id: 'home.awareness.friendly-covid.buttonText' })}
              onButtonClick={this.handleAwarenessFcclick}
              preset={3}
              text={translate({ id: 'home.awareness.friendly-covid.text' })}
              title={translate({ id: 'home.awareness.friendly-covid.title' })}
              trackName="friendly-covid-cancel"
              trackPage="home"
            />
          </AwarenessBlock>
          <HomeHowPaulcamperWorks
            locale={locale}
            totalCampers={formattedTotalCampers}
          />
          <div className={styles.renterSteps}>
            <HomeRenterSteps/>
          </div>
          {!doHideHomeCities && // temporarily hidden section for .it,co.uk,.es domains [.it uncomment][.es uncomment][.co.uk uncomment]
          <Grid>
            <Row>
              <Col xs={12}>
                <div className={styles.cities}>
                  <HomeCities country={app.country.code} locale={locale}/>
                </div>
              </Col>
            </Row>
          </Grid>}
          <div className={styles.community}>
            <HomeCommunity locale={locale}/>
          </div>
          <div className={styles.sectionText}>
            <Grid>
              <Row>
                <Col xs={12}>
                  <FormattedHTMLMessage id="home.text1"/>
                </Col>
              </Row>
            </Grid>
          </div>
          {locale.language === DE &&
          <div className={styles.posts}>
            <Grid>
              <Row>
                <Col xs={12}>
                  <HomePosts breakpoint={app.breakpoint} locale={locale} posts={content.pagesList}/>
                </Col>
              </Row>
            </Grid>
          </div>}
          <div className={styles.contacts}>
            <Grid>
              <Row>
                <Col xs={12}>
                  <SectionQuestions isLenderView/>
                </Col>
              </Row>
            </Grid>
          </div>
          <div className={styles.team}>
            <HomeTeam locale={locale}/>
          </div>
          <div className={styles.sectionText}>
            <Grid>
              <Row>
                <Col xs={12}>
                  <FormattedHTMLMessage id="home.text2"/>
                </Col>
              </Row>
            </Grid>
          </div>
          <Footer location={this.props.location} trustBox={false}/>
          {env.AUTOTEST_MODE !== 'true' &&
          // eslint-disable-next-line react/no-danger
          <script defer dangerouslySetInnerHTML={{ __html: customerSupportWidget(locale.code) }}/>}
        </div>
      </nestedStatus>
    )
  }
}

export default compose(
  connect(
    state => ({
      app: state.app,auth: state.auth,env: getEnv(state),search: state.search,content: state.content,fromDate: state.search.fromDate,lastDraftCamperId: lastDraftCamperIdSelector(state),userId: userIdSelector(state),locale: state.app.locale,tillDate: state.search.tillDate,{ setFilter,setFilters,setLocation }),withStyles(styles),intl(),)(Home)

src/server/createRenderApp.js

...
      // Critical CSS
      const css = new Set()

      // Global (context) variables that can be easily accessed from any React component
      // https://facebook.github.io/react/docs/context.html
      const appContext = {
        // Enables critical path CSS rendering
        // https://github.com/kriasoft/isomorphic-style-loader
        insertCss: (...styles) => styles.forEach(style => css.add(style._getCss())),}
      const url = req.originalUrl || req.url
      // fix query and search
      const location = parseUrl(url,true)
      const routes = getRoutes(store)
      const helpers = { client }

      loadOnServer({
        store,location,routes,helpers,})
        .then(() => {
          appContext.translations = translations[domainConfig.locale]
          const routerContext = {}

          const component = (
            <App context={appContext}>
              <Provider store={store} key="provider">
                <ThemeProvider>
                  <StaticRouter location={location} context={routerContext}>
                    <ReduxAsyncConnect helpers={helpers} routes={routes} />
                  </StaticRouter>
                </ThemeProvider>
              </Provider>
            </App>
          )
...

请找到在 Sentry here 上捕获的错误的屏幕截图。

任何想法或建议肯定会有所帮助。提前致谢。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)