简体   繁体   English

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

[英]SSR React Application using loadable library, throws “Cannot convert undefined or null to object” error intermittently on the server for Home Route

I have a (Server Side Rendered)SSR application built using React & Redux.我有一个使用 React 和 Redux 构建的(服务器端渲染)SSR 应用程序。 I also use the loadable library.我也使用可加载库。 The application is hosted on Heroku and uses Cloudflare caching.该应用程序托管在 Heroku 上,并使用 Cloudflare 缓存。

The application intermittently ends up in a 500 error on Heroku and I am not able to find the root cause of the error.应用程序间歇性地在 Heroku 上出现 500 错误,我无法找到错误的根本原因。 Below is the error message下面是错误信息

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)

Since this is happening intermittently, I am not able to find the root cause of why this is happening.由于这是间歇性发生的,我无法找到发生这种情况的根本原因。 Below are the details for the Home Component where the error occurs以下是发生错误的 Home 组件的详细信息

src/routes/HomeRoute.js 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', 'Pagination', 'Total'], 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 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', 'Pagination', 'Total'], 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' },
        { rel: 'alternate', 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, FR, 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 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>
          )
...

Please find the screenshot of the error captured on Sentry here .请在此处找到在 Sentry 上捕获的错误的屏幕截图。

Any thoughts or suggestion will definitely help.任何想法或建议肯定会有所帮助。 Thank you in advance.先感谢您。

I was running into the same issue and it turned out that the getPrototypeOf() was being called on the anonymous function (see top of the stack trace at getPrototypeOf (<anonymous>) ).我遇到了同样的问题,结果发现getPrototypeOf()正在匿名 function 上被调用(请参阅at getPrototypeOf (<anonymous>)的堆栈跟踪顶部)。

In my case, this was happening because I had a forwardRef on my component I was using in loadable (equivalent to your Home component).就我而言,发生这种情况是因为我在可加载的组件上使用了forwardRef (相当于您的Home组件)。 This was what was making it an anonymous function.这就是使它成为匿名 function 的原因。 I'm guessing there is a difference between NodeJS and browser JS.我猜 NodeJS 和浏览器 JS 是有区别的。 This was on loadable components version 5.13.这是在可加载组件版本 5.13 上。 Can't tell if you're using a forwardRef or not, as the beginning of your Home component is missing.无法判断您是否使用了forwardRef ,因为缺少Home组件的开头。

Very possible your issue was a different cause, but the solution may be the same.您的问题很可能是不同的原因,但解决方案可能是相同的。 I ended up being setting SSR to false in the loadable component like so:我最终在可加载组件中将 SSR 设置为 false,如下所示:

const Home = loadable(() => {
    import(/* webpackChunkName: "home-route" */ './Home'), {ssr: false}
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM