SSR后路由无法正常工作

时间:2018-02-15 07:02:15

标签: reactjs express

Routing to SSR app

的后续问题中的这个问题

问题陈述 - 当我运行我的服务器时,它会加载主页,然后当我在浏览器中输入localhost:3001/about_us时,它会加载组件(我可以看到它完全加载)和然后它突然再次呈现主页。

AboutUs是通过react-loadable加载的AsyncComponent。

我已经在上面的链接上共享了服务器端安装文件。

项目结构如下:

  • 构建
  • 公共
  • 服务器
  • src(反应应用)

客户端代码

的src / index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {ConnectedRouter} from 'react-router-redux';
import store, { history } from './store';

import './index.css';
import './main.css';
import App from './containers/app';
import 'babel-polyfill';
import Loadable from 'react-loadable';

window.onload = () => {
    Loadable.preloadReady().then(() => {
        ReactDOM.hydrate(
            <Provider store={store}>
                <ConnectedRouter history={history}>
                      <App />
                </ConnectedRouter>
            </Provider>,
             document.getElementById('root')
        );
    });
};

的src / store.js

import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import createHistory from 'history/createMemoryHistory';
import rootReducer from './reducers';

export const history = createHistory();

const initialState = {};
const enhancers = [];
const middleware = [thunk, routerMiddleware(history)];



const composedEnhancers = compose(applyMiddleware(...middleware), ...enhancers);

const store = createStore(rootReducer, initialState, composedEnhancers);

export default store;

容器/应用/ index.js

import Loadable from 'react-loadable';
import { LoadingSuccess, getMetaData } from '../../reducers/homepage';


const AsyncHome = Loadable({
  loader: () => import(/* webpackChunkName: "root" */'../public/home'),
  loading: () => <div>loading...</div>,
  modules: ['root']
});


const AsyncContactUs = Loadable({
  loader: () => import(/* webpackChunkName: "contact-us" */'../public/contact-us'),
  loading: () => <div>loading...</div>,
  modules: ['contact-us']
});

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            path: null
        };
    }

    componentDidMount() {
        window.scrollTo(0, 0);
        this.props.LoadingSuccess();
    }

    componentWillReceiveProps(nextProps) {
        window.scrollTo(0, 0);
        var path = ((nextProps.history || {}).location || {}).pathname;
        if (path && path != this.state.path && Constant.ignoreMetaTags.indexOf(path) == -1) {
            // this.props.getMetaData(path);
            // this.setState({
            //  path: path
            // });
        }
    }

    navigateTo(route) {
        if (route.length) {
            this.props.navigateTo(route);
        }
    }

    render() {
        const { isAuthenticated, navigateTo, errors, ForgotPasswordSuccess, forgotPasswordError, forgotPasswordSuccessMessage, metaDataPayload, metaDataError } = this.props;
        return (
            <Container>
                {this.setMetaTags()}
                <Header isAuthenticated={isAuthenticated} navigateTo={navigateTo} />
                <View>
                    <Switch>
                        <Route path="/" exact component={() => <AsyncHome />} />
                        <Route path="/home" component={() => <h1>Home</h1>} />


                        <Route path="/contact_us" component={() => <AsyncContactUs />} />
                        <PrivateRoute path="/protected" component={Authenticated} />
                    </Switch>
                </View>
                <Footer />
            </Container>
        );
    }
}

const mapsStateToProps = ({
  homepageReducer: { homepageLoaded, metaDataPayload, metaDataError },
    authReducer: {
    isAuthenticated,
        errors,
        forgotPasswordError,
        forgotPasswordSuccessMessage
  },
    contactUsReducer: { locations, error },
    location
}) => ({
        homepageLoaded,
        isAuthenticated,
        location,
        errors,
        forgotPasswordError,
        forgotPasswordSuccessMessage,
        metaDataPayload,
        metaDataError
    });

const mapDispatchToProps = dispatch => ({
    navigateTo: route => dispatch(push(route)),
    LoadingSuccess: () => dispatch(LoadingSuccess()),
    ForgotPasswordSuccess: payload => dispatch(ForgotPasswordSuccess(payload))});

export default withRouter(connect(mapsStateToProps, mapDispatchToProps)(App));

在调试时,它可以从reducer调用LoadSuccess作为最后一次调用,然后呈现主页。我的猜测是跟随客户端和服务器历史不匹配但不能以某种方式消除手中的问题。

更新 -

服务器端代码 -

index.js

从'express'导入快递;

import serverRenderer from './middleware/renderer';
import Loadable from 'react-loadable';

const PORT = 3001;
const path = require('path');
const app = express();
const router = express.Router();

router.use('^/$', serverRenderer);
app.use('/static', express.static(path.join(__dirname, 'assets')));
router.use(express.static(
    path.resolve(__dirname, '..', 'build'),
    { maxAge: '30d' },
));
router.use('*', serverRenderer);
app.use(router);
Loadable.preloadAll().then(() => {
    app.listen(PORT, (error) => {
        if (error) {
            return console.log('something bad happened', error);
        }

        console.log('listening on ' + PORT + '...');
    });
});

renderer.js

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import store from '../../src/store';
import {createMemoryHistory } from 'history';
import { ServerStyleSheet } from 'styled-components';
import Loadable from 'react-loadable';
import manifest from '../../build/asset-manifest.json';
import App from '../../src/containers/app';
// import request from 'request';
import axios from 'axios';
var request = axios.create({
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json'
  }
});

const path = require('path');
const fs = require('fs');

const modules = [];
const extractAssets = (assets, chunks) => Object.keys(assets)
    .filter(asset => chunks.indexOf(asset.replace('.js', '')) > -1)
    .map(k => assets[k]);

export default (req, res, next) => {
    const url = req.originalUrl;
    const history = createMemoryHistory({
      initialEntries: [req.originalUrl],
      initialIndex: 0
    });
    // const history = syncHistoryWithStore(createMemoryHistory, store)

    try {
        if (req.originalUrl !== '/' && req.originalUrl.indexOf('[') === -1) {
            request.get('http://localhost:9000/api/v1/public/seo/pages' + url ).then(function (response) {
                if (response.data.success){
                    // console.log('response.data', response.data);
                    const { title, meta_description, meta_keywords, noindex, additional_meta} = response.data.page;
                    console.log(title, meta_description, meta_keywords, noindex);
                    const additional_meta_string = additional_meta.map(function (item) {
                        return <meta key={item.name} name={item.name} content={item.content} property={item.property} />;
                    });
                    const metaString = `<title>${title}</title>
                                        <meta name="description" content=${meta_description} />
                                        <meta name="keywords" content=${meta_keywords} />
                                        ${noindex ? <meta name="robots" content="noindex" /> : null} ${additional_meta_string}`;

                    const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html');
                    fs.readFile(filePath, 'utf8', (err, htmlData) => {
                        if (err) {
                            console.error('err', err);
                            return res.status(404).end();
                        }
                        const sheet = new ServerStyleSheet();
                        const body = ReactDOMServer.renderToString(
                            sheet.collectStyles(<Provider store={store}>
                              <ConnectedRouter history={history}>
                                  <Loadable.Capture report={m => modules.push(m)}>
                                       <App />
                                  </Loadable.Capture>
                              </ConnectedRouter>
                            </Provider>)
                        );
                        const styleTags = sheet.getStyleTags();
                        const extraChunks = extractAssets(manifest, modules)
                        .map(c => `<script type="text/javascript" src="/${c}"></script><script>window.REDUX_STATE = ${reduxState};</script>`);
                            return res.send(
                                htmlData.replace(
                                        '</head>',
                                        `${styleTags}</head>`
                                    )
                                    .replace(/__OG_TITLE__/g, title)
                                    .replace(/__OG_DESCRIPTION__/g, meta_description)
                                    .replace(
                                    '<div id="root"></div>',
                                    `<div id="root">${body}</div>`
                                ).replace(
                                    '</body>',
                                    extraChunks.join('') + '</body>'
                                )
                            );
                    });
                } else {
                    console.log('response.error', response.data.messages);
                }
            }).catch(function (error) {
               console.log('API_ERROR', error);
               return error;
            });
        } else {
            const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html');
            fs.readFile(filePath, 'utf8', (err, htmlData) => {
                if (err) {
                    console.error('err', err);
                    return res.status(404).end();
                }
                const sheet = new ServerStyleSheet();
                const body = ReactDOMServer.renderToString(
                    sheet.collectStyles(<Provider store={store}>
                      <ConnectedRouter history={history}>
                          <Loadable.Capture report={m => modules.push(m)}>
                               <App />
                          </Loadable.Capture>
                      </ConnectedRouter>
                    </Provider>)
                );
                const styleTags = sheet.getStyleTags();
                const reduxState = JSON.stringify(store.getState());
                const extraChunks = extractAssets(manifest, modules)
                .map(c => `<script type="text/javascript" src="/${c}"></script><script>window.REDUX_STATE = ${reduxState};</script>`);
                    return res.send(
                        htmlData.replace(
                                '</head>',
                                `${styleTags}</head>`
                            ).replace(
                            '<div id="root"></div>',
                            `<div id="root">${body}</div>`
                        ).replace(
                            '</body>',
                            extraChunks.join('') + '</body>'
                        )
                    );
            });
        }

    } catch (e) {
      console.log('getInfo_ERROR', e);
      next(e);
    }
};

1 个答案:

答案 0 :(得分:0)

是的,您的状态不同步,这正是导致此问题的原因。您需要做的是将redux存储的状态发送到客户端并初始化它。

以下是我的工作:

在服务器上:

res.write(
  `<script>window.__INITIALSTATE__ = ${JSON.stringify(
    store.getState()
  )};</script>`
)

将脚本标记中的状态包含在HTML中。

然后在客户端上,您想要读取它并将其设置为初始状态。

const initialState = window && window.__INITIALSTATE__ || {}
const store = createStore(
  config.redux.reducer,
  initialState,
  enhancer
);

在您的情况下,您仍然缺少src / store.js

<强>的src / store.js

 const initialState = window && window.REDUX_STATE || {};