如何为React Router 4实现服务端渲染?求教程及代码迁移资源
Hey there! Let's walk through how to migrate your server-side rendering (SSR) code from the older React Router version (used in Pro MERN Stack) to React Router 4. I'll break this down step by step since v4 completely reworked SSR mechanics—goodbye match and RouterContext, hello StaticRouter!
Step 1: Update Dependencies
First, swap out the old react-router package for react-router-dom (v4.x), which now handles all DOM-specific routing logic:
npm uninstall react-router npm install react-router-dom@4
Step 2: Rewrite Server-Side Rendering Logic
The biggest change is ditching the match function and RouterContext for StaticRouter—a Router built explicitly for server-side use. Here's how to refactor your renderedPageRouter.jsx:
Old Code (From the Book):
import React from 'react'; import { renderToString } from 'react-dom/server'; import { match, RouterContext } from 'react-router'; import routes from './routes'; // Typical Express route handler app.get('*', (req, res) => { match({ routes, location: req.url }, (err, redirectLocation, renderProps) => { if (err) { res.status(500).send(err.message); } else if (redirectLocation) { res.redirect(302, redirectLocation.pathname + redirectLocation.search); } else if (renderProps) { const html = renderToString(<RouterContext {...renderProps} />); res.send(`<div id="root">${html}</div>`); } else { res.status(404).send('Not Found'); } }); });
New React Router 4 Code:
import React from 'react'; import { renderToString } from 'react-dom/server'; import { StaticRouter } from 'react-router-dom'; // Import your root app component (where you define routes) import App from './App'; // Updated Express route handler app.get('*', (req, res) => { // Create a context object to capture redirects/404 statuses const context = {}; // Use StaticRouter instead of match + RouterContext const html = renderToString( <StaticRouter location={req.url} context={context}> <App /> </StaticRouter> ); // Handle redirects captured by the context if (context.url) { res.redirect(302, context.url); return; } // Handle 404s (set in your NotFound component) const statusCode = context.status === 404 ? 404 : 200; res.status(statusCode).send(` <!DOCTYPE html> <html> <body> <div id="root">${html}</div> <script src="/client-bundle.js"></script> </body> </html> `); });
Step 3: Update Client-Side Routing
On the client, replace the old Router with BrowserRouter:
// client/index.jsx import React from 'react'; import { render } from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import App from './App'; render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById('root') );
Step 4: Define Routes in Your App Component
React Router 4 uses component-based routing instead of route arrays. Here's how to structure your App.jsx:
import React from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; import Home from './Home'; import About from './About'; import NotFound from './NotFound'; const App = () => ( <Switch> {/* Exact match for home */} <Route exact path="/" component={Home} /> {/* About page */} <Route path="/about" component={About} /> {/* Redirect example */} <Redirect from="/old-about" to="/about" /> {/* Catch-all 404 */} <Route component={NotFound} /> </Switch> ); export default App;
Step 5: Handle 404 Status on the Server
To let the server know when a 404 occurs, update your NotFound component to set the context status:
import React from 'react'; const NotFound = ({ staticContext }) => { // staticContext only exists during SSR if (staticContext) { staticContext.status = 404; } return <h1>404 - Page Not Found</h1>; }; export default NotFound;
Step 6: Data Prefetch (Optional)
If your original code included data prefetching for SSR, you can add static loadData methods to your route components and call them server-side. For simplicity, use the official react-router-config package to streamline this:
- Install it:
npm install react-router-config - Define routes as an array:
// routes.js import Home from './Home'; import About from './About'; import NotFound from './NotFound'; const routes = [ { path: '/', exact: true, component: Home, loadData: () => fetch('/api/home-data').then(res => res.json()) }, { path: '/about', component: About, loadData: () => fetch('/api/about-data').then(res => res.json()) }, { component: NotFound } ]; export default routes; - Update your server to prefetch data:
import { matchRoutes } from 'react-router-config'; import routes from './routes'; app.get('*', async (req, res) => { const context = {}; // Get all matching routes and their data fetch functions const matches = matchRoutes(routes, req.url); const dataPromises = matches.map(({ route }) => route.loadData ? route.loadData() : Promise.resolve(null) ); // Wait for all data to load await Promise.all(dataPromises); // Render with preloaded data const html = renderToString( <StaticRouter location={req.url} context={context}> <App routes={routes} /> </StaticRouter> ); // Send response (same as before) if (context.url) { res.redirect(302, context.url); return; } const statusCode = context.status === 404 ? 404 : 200; res.status(statusCode).send(`<div id="root">${html}</div>`); });
Key Takeaways
- No more
match/RouterContext: Replace withStaticRouter(server) andBrowserRouter(client) - Component-based routing: Routes are now nested components instead of arrays (or use
react-router-configfor array-based setup) - Context object: Captures redirects and status codes for server-side handling
- Data prefetch: Use static component methods or
react-router-configto load data before rendering
内容的提问来源于stack exchange,提问作者Maicmi

