Не для слабонервных говорю вам.
Но если вас интересуют преимущества SSR (а именно SEO и более быстрая загрузка), и вы не хотите полностью обновлять свой код (потому что вы это сделаете), чтобы получить такие вещи, как окно, работа с документами (Node не знает о них , сюрприз!) и даже более простых шаблонов, таких как async await (шок!), работающих с babel, вот вам небольшой совет.
Идея SSR такова:
- Вам не нужно приложение только на JS. Вам нужен HTML-код, чтобы сканеры могли понять, о чем ваш веб-сайт, а именно о статическом содержании.
- Когда браузер запрашивает страницу с вашего веб-сайта (GET / home), ваш сервер должен вернуть некоторый html
- Последующие взаимодействия со страницей (если вы не перезагружаете / не открываете другую страницу в новой вкладке) обрабатываются через JS (bundle.js в React).
Выглядит это примерно так:
import Express from 'express'; import React from 'react'; import {createStore} from 'redux'; import {Provider} from 'react-redux'; import {reducerFn} from './reducers'; import {renderToString} from 'react-dom/server'; import {routes} from './app'; import {StaticRouter} from "react-router-dom"; import {Helmet} from "react-helmet"; const app = Express(); const port = 8092; // Serve static files app.use("/assets", Express.static('assets')); app.use("/static", Express.static('static')); // This is fired every time the server side receives a request app.use(handleRender); // We are going to fill these out in the sections to follow function handleRender(req, res) { const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl; console.log('fullUrl: ', fullUrl); console.log('req.url: ', req.url); // Create a new Redux store instance const store = createStore(reducerFn); // Render the component to a string const html = renderToString( <Provider store={store}> <StaticRouter location={req.url} context={{}}> {routes} </StaticRouter> </Provider> ); const helmet = Helmet.renderStatic(); // Grab the initial state from our Redux store const preloadedState = store.getState(); // Send the rendered page back to the client res.send(renderFullPage(helmet, html, preloadedState)); } function renderFullPage(helmet, html, preloadedState) { return ` <!doctype html> <html> <head> ${helmet.title.toString()} ${helmet.meta.toString()} ${helmet.link.toString()} ${helmet.style.toString()} ${helmet.script.toString()} <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="icon" href="https://imageserver.homedruid.com/v1/image/get?id=1398" type="image/png" /> <script type="text/javascript" async src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB_6Ly7ovRtYqb_p7QxxRSV3WnQh3b1e6Y&libraries=places"></script> </head> <body style="margin: 0"> <div id="root">${html}</div>\n <script> // WARNING: See the following for security issues around embedding JSON in HTML: // http://redux.js.org/recipes/ServerRendering.html#security-considerations window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')} </script> <script src="/assets/bundle.js"></script> </body> </html> `; } app.listen(port);
Посмотрите на магию ниже:
// Magic 1 renderToString() // Magic 2 <script src="/assets/bundle.js"></script>
Мы используем Node в качестве браузера для выполнения JS, рендеринга макета в виде html и его обслуживания. Мы также отправляем код bundle.js (наш SPA), который будет перезаписывать визуализированный макет html и снова отображать компоненты в JS. Не думал об этом, когда начинал, но это довольно просто и эффективно. Основная проблема в SSR заключается в том, чтобы заставить работать эти два волшебных элемента.
Итак, вот хитрость. Если вы не можете заставить SSR работать, потому что это слишком болезненно, сделайте следующее. Напишите простой сервер nginx / Express, который обслуживает статический html (вы можете получить его, отрисовав SPA и получив document.innerHTML) для домашней страницы вместе с bundle.js.
И это все !
Хотите увидеть это в действии: https://www.heloprotocol.in/