首页 > 解决方案 > DOMException:无法在“历史”上执行“replaceState”:具有 URL 的历史状态对象

问题描述

在谷歌上打开网页的缓存版本时,反应应用程序出现以下错误。

DOMException:无法在“历史”上执行“replaceState”:无法在源为“https://webcache.googleusercontent.com”和 URL 为“https”的文档中创建 URL 为“https://projecturl”的历史状态对象: //webcache.googleusercontent.com/search?q=cache:X4dz2ukZZAYJ:https://projecturl/+&cd=1&hl=en&ct=clnk&gl=in'

在此处输入图像描述

在我们的应用程序中,我们使用 react-router-dom 并实现了服务器端渲染。在谷歌搜索中打开带有缓存选项的页面时,它首先加载页面几秒钟,然后在控制台中显示带有上述错误的空白页面。

在寻找解决方案时,我发现https://github.com/ReactTraining/react-router/issues/5801 与我的问题相关,但没有解决方案。

更新 1:

这里问了同样的问题,但对于 Angular。虽然我无法理解答案中解释的内容以及它与我的问题的关系。

我们正在使用React Loadable SSR Add-on来对我们的 react 应用程序进行服务器端渲染。

更新 2:

在用于服务器端渲染的 npm 包的 Git repo 上打开了相同的问题。 问题已打开

更新 3:

当我通过禁用安全性在谷歌浏览器中打开它时,该页面工作正常。因此,它不应该与我的代码有关。

此外,在 bing 搜索引擎缓存版本中打开时会出现不同的错误:

脚本资源位于重定向后面,这是不允许的。

在此处输入图像描述

在 bing 和 yahoo 搜索引擎上,404 页面出现在缓存版本中。

更新 4:

这是路由文件的外观:

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Loadable from 'react-loadable';
import {
  OTPNew,
  LoginNewFb,
  OnboardingWrapper,
  PageNotFound,
  SignUpSpecialty,
  SignUpDetails,
  Feed,
} from './lazy';

const RootComponent = Loadable({
  loader: () =>
    import(/* webpackChunkName: "rootcomp" */ '../components/RootComponent'),
  loading: () => null,
  modules: ['../components/RootComponent'],
  webpack: () => [require.resolveWeak('../components/RootComponent')],
});

const signupRoutes = [
  {
    path: '/login/otp',
    component: OTPNew,
  },
  {
    path: '/login',
    component: LoginNewFb,
  },
  {
    path: '/signup/details',
    component: SignUpDetails,
  },
  {
    path: '/signup',
    component: SignUpSpecialty,
  },
];

const Routes = () => {
  return (
    <Switch>
      {signupRoutes.map(sRoute => (
        <Route
          key={sRoute.path}
          path={sRoute.path}
          render={routeProps => (
            <OnboardingWrapper>
              <sRoute.component {...routeProps} />
            </OnboardingWrapper>
          )}
        />
      ))}
      <Route path="/feed" component={Feed} />
      <Route path="/" component={RootComponent} />
      <Route path="*" component={PageNotFound} />
    </Switch>
  );
};

export default Routes;

根组件.js

    import React from 'react';
    import { Switch, Route } from 'react-router-dom';
    import { useSelector } from 'react-redux';
    import MainComponent from './MainComponent';
    import { DiscussionComponent, HomePage, PageNotFound } from '../routes/lazy';
    import { useIsMobile } from '../actions/VerifyMobileAction';
    import rootRoutes from '../routes/rootRoutes';
    import quizRoutes from '../routes/quizRoutes';
    import { parseQueryParameter, getAPIHost } from '../helpers/helperFunctions';
    
    function cacheQueryParser(query, projectCanonnicalAddr) {
      return query
        .split(projectCanonnicalAddr)
        .pop()
        .split('+')[0];
    }
    
    function getPageOrNotFound(location) {
      const queryObject = parseQueryParameter(location.search);
      const projectCanonnicalAddr = getAPIHost();
    
      if (
        location.pathname === '/search' &&
        'q' in queryObject &&
        queryObject.q.indexOf('cache') === 0 &&
        queryObject.q.indexOf(projectCanonnicalAddr) > -1
      ) {
        const replacer = cacheQueryParser(queryObject.q, projectCanonnicalAddr);
        return {
          ComponentRender: null,
          replacer,
        };
      }
      return {
        ComponentRender: PageNotFound,
        replacer: null,
      };
    }

const RootComponent = () => {
  const { OtpVerified } = useSelector(store => store.authenticationReducer);
  const isMobileViewport = useIsMobile();

  function logicForHomeRoute() {
    if (OtpVerified) {
      return {
        component: DiscussionComponent,
      };
    }
    return {
      renderHeaderDesktop: false,
      renderHeaderMobile: false,
      renderFooter: false,
      renderSideProfile: false,
      component: HomePage,
    };
  }

  const typeOfAppClassName = `${
    isMobileViewport ? 'mobile' : 'desktop'
  }-viewport-app`;

  return (
    <div className={typeOfAppClassName}>
      <Switch>
        <Route
          exact
          path="/"
          render={() => <MainComponent {...logicForHomeRoute()} />}
        />
        {[...quizRoutes, ...rootRoutes].map(sRoute => (
          <Route
            key={sRoute.path}
            path={sRoute.path}
            render={props => {
              const { location, history } = props;
              if (sRoute.path === '/:alternate_username') {
                if (location.pathname.startsWith('/dr') === false) {
                  const { replacer, ComponentRender } = getPageOrNotFound(
                    location
                  );
                  if (ComponentRender) {
                    return <ComponentRender />;
                  }
                  history.replace(replacer);
                  return null;
                }
              }
              return <MainComponent {...props} {...sRoute} />;
            }}
          />
        ))}
      </Switch>
    </div>
  );
};

export default RootComponent;

更新 5

控制台中显示另一个错误:

获取脚本时收到错误的 HTTP 响应代码 (404)。

标签: reactjsreact-routerreact-router-domserver-side-rendering

解决方案


由于您在“空白页面”之前看到您的正常页面,我的猜测是,首先,您会看到网站的 SSR 版本,然后在加载脚本后,您会看到“空白页面”,因为 CORS 限制。

作为一种解决方案,您可以仅为您的域加载脚本,并将 SSR 版本保留给google/bing/yahoo,而没有机会加载这些脚本并破坏站点。

答案基于react-loadable-ssr-addon的示例,这里的想法是检查window.location.origin您的域的来源。否则,只需完全跳过这些文件。

原始示例:

res.send(`
  <!doctype html>
  <html lang="en">
    <head>...</head>
    ${styles.map(style => {
      return `<link href="/dist/${style.file}" rel="stylesheet" />`;
    }).join('\n')}
    <body>
      <div id="app">${html}</div>
      ${scripts.map(script => {
        return `<script src="/dist/${script.file}"></script>`
      }).join('\n')}
  </html>
`);

这是我的动态加载示例:

res.send(`
  <!doctype html>
  <html lang="en">
    <head>...</head>
    ${styles.map(style => {
      return `<link href="/dist/${style.file}" rel="stylesheet" />`;
    }).join('\n')}
    <body>
      <div id="app">${html}</div>
      <script>
        function loadScript(url) {
          var s = document.createElement("script");
          s.src = url; document.body.appendChild(s);
        }

        if (
          window.location.origin === "https://example.com" ||
          window.location.origin === "http://localhost" // for development purpose
        ) {
          ${scripts.map(script => {
            return `loadScript("/dist/${script.file}");`
          }).join('\n')}
        }
      </script>
  </html>
`);

当然,你的应用程序中有你自己的代码,这就是为什么我不能给你一个完整的解决方案。您应该更改代码以遵循此想法。

至于其他错误。部分地,它们也基于 CORS 限制,其中之一来自https://connect.facebook.net/en_US/fbevents.js. 与您的服务工作人员文件相关的 404 错误是由 google 的内部“webcache”算法引起的,我不确定您是否可以对此做些什么。

无论如何,这些错误不应干扰缓存站点的正确显示,因为它们不是您看到空白页面的原因。在我看来,当然,这是基于截图。


推荐阅读