如何在反应服务器端渲染中添加额外的快速路线以用作后端

How to add additional express routes in react server side rendering to use as backend

在底部找到 编辑

我一直在研究很多教程,甚至是位于此处的 React 训练服务器端渲染的文档:https://reacttraining.com/react-router/web/guides/server-rendering

对我来说一直是一个问题的是使用其他路由以获得完整的堆栈应用程序。例如,我们将使用 MERN 堆栈 (mongodb express React/Redux nodejs)。假设我们在

中有两条路线

app.js 像这样:

app.use('/users', users);
app.use('/posts', posts);

并且我们的服务器端渲染就像这些教程中的大多数一样设置,实际上我找不到没有这样设置的教程,为了节省时间,我将把 SSR 代码放在哪里。如果您不知道如何进行 SSR,请查看我上面提供的 link 中的文档。我认为可能不需要,但如果需要请告诉我,我可以添加一个简单的设置。

类似于:

app.get('*', (req, res) => { //server side rendering code here});

所以这里的问题是它命中了服务器端渲染的每个路由和内部 React Router 4,大多数人会使用 matchRoutes 函数并传入他们用于反应端的路由.

好的,我已经解释了很多,但不是真正的问题所在:

问题:在反应方面,假设使用一个动作,我们将在这里使用 axios 作为一个简单的例子。

如果我这样做:

axios.get('/posts')
.then(response => { 
 console.log(response.data);
 })
.catch(err => {
 console.log(err);
 });

这就遇到了问题。对于任何熟悉 express 和 SSR 的人,您都会知道为什么。 app.get('*') 正在访问每条路由,或者更确切地说,每条路由请求都会经过这里,问题是当我们向 /posts 发送请求时,会发生两种情况之一。

案例 1:在反应端的路由文件中有一个客户端路由 /posts,这种情况将 return 服务器端 html 作为 response.data 这显然不是我们想要的。

情况 2: 反应端的路由文件中没有客户端路由 /posts,这种情况将 return 404 错误或其他如果您正确设置了 SSR,类似于找不到路由或页面。

我需要一种既能使用我的后端,又能使用 SSR 进行反应的方法。

可能的解决方案?

  1. 使用某种类型的代理,如果 /api/routename 然后使用代理,这样您的呼叫就不会通过 app.get('*').

  2. 或仅在个别路线上进行 SSR 的某种方式。

  3. 或排除某些路由,但可以使用所有客户端路由。在此我的意思是,如果我有客户端 /users 和一个快递方 /users,我不希望客户端在我排除 /users 时中断,但有些如何仍然排除快递边.

我不太确定这些解决方案是否可行,但我需要能够使用我的快速路线,但我找不到具体的方法,或者什么是最佳实践。

编辑:

服务器端app.js:

require('babel-core/register')({
  presets: ['env', 'react', 'stage-0', 'stage-1']
});

const pkg_json = require('./package.json');
const vertex = require('vertex360')({ site_id: pkg_json.app });
var renderer = require('./renderer.js');

// initialize app
const app = vertex.app();

// import routes
const index = require('./routes/index');
const api = require('./routes/api');
const users = require('./routes/users');

// set routes
app.use('/api/users', users);

// hopefully will be used on every Route, this should handle SSR RR4
app.use(renderer);

module.exports = app;

renderer.js:

import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import serialize from 'serialize-javascript';
import { Helmet } from 'react-helmet';
import { matchRoutes } from 'react-router-config';

import routes from './src/routes';
import createStore from './src/stores';

function handleRender(req, res) {
  const store = createStore.configure(null); // create Store in order to get data from redux

  const promises = matchRoutes(routes, req.path)
    .map(({ route }) => {
      // Matches the route and loads data if loadData function is there
      return route.loadData ? route.loadData(store) : null;
    })
    .map(promise => {
      if (promise) {
        return new Promise((resolve, reject) => {
          promise.then(resolve).catch(resolve); // lets all data load even if route fails
        });
      }
    });

  Promise.all(promises).then(() => {
    const context = {};
    if (context.url) {
      return res.redirect(301, context.url); // redirect for non auth users
    }

    if (context.notFound) {
      res.status(404); // set status to 404 for unknown route
    }

    const content = renderToString(
      <Provider store={store}>
        <StaticRouter location={req.path} context={context}>
          <div>{renderRoutes(routes)}</div>
        </StaticRouter>
      </Provider>
    );

    const initialState = serialize(store.getState());

    const helmet = Helmet.renderStatic();

    res.render('index', { content, initialState, helmet });
  });
}

module.exports = handleRender;

React 入口点:

import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { renderRoutes } from 'react-router-config';
import serialize from 'serialize-javascript';
import { Helmet } from 'react-helmet';
import { matchRoutes } from 'react-router-config';

import routes from './src/routes';
import createStore from './src/stores';

function handleRender(req, res) {
  const store = createStore.configure(null); // create Store in order to get data from redux

  const promises = matchRoutes(routes, req.path)
    .map(({ route }) => {
      // Matches the route and loads data if loadData function is there
      return route.loadData ? route.loadData(store) : null;
    })
    .map(promise => {
      if (promise) {
        return new Promise((resolve, reject) => {
          promise.then(resolve).catch(resolve); // lets all data load even if route fails
        });
      }
    });

  Promise.all(promises).then(() => {
    const context = {};
    if (context.url) {
      return res.redirect(301, context.url); // redirect for non auth users
    }

    if (context.notFound) {
      res.status(404); // set status to 404 for unknown route
    }

    const content = renderToString(
      <Provider store={store}>
        <StaticRouter location={req.path} context={context}>
          <div>{renderRoutes(routes)}</div>
        </StaticRouter>
      </Provider>
    );

    const initialState = serialize(store.getState());

    const helmet = Helmet.renderStatic();

    res.render('index', { content, initialState, helmet });
  });
}

module.exports = handleRender;

感谢@VivekN,你这样做的方式是确保你的 app.get('*') 在每条声明的路线下方。这应该允许您的其他路线首先通过。