├── .gitignore
├── LICENSE.md
├── README.md
├── bin
└── react-scripts-ssr.js
├── config
├── paths.js
└── webpack.config.server.js
├── examples
├── codeSplitting
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── LazyComponent.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ ├── server.js
│ │ └── serviceWorker.js
│ └── yarn.lock
└── helloWorld
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
│ └── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ ├── server.js
│ └── serviceWorker.js
├── index.js
├── package.json
├── scripts
├── build-server.js
├── proxy.js
└── start.js
├── src
├── middlewares.js
└── render.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present LeanJS
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-scripts-ssr
2 |
3 | Create React apps with server-side rendering (SSR) with no configuration
4 |
5 | # Installation
6 |
7 | `npm install react-scripts-ssr --save-dev`
8 |
9 | # Getting started
10 |
11 | ## Steps
12 |
13 | ### Step 1
14 |
15 | In the scripts section of your package.json:
16 |
17 | - Replace `"start": "react-scripts start"` with `"start": "react-scripts-ssr start",`
18 | - Add `"build-server": "react-scripts-ssr build-server",`
19 |
20 | ### Step 2
21 |
22 | - `npm install express --save`
23 | - Create the following file in src/server.js
24 |
25 | ```javascript
26 | const React = require("react");
27 | const express = require("express");
28 | const { createSSRMiddleware } = require("react-scripts-ssr");
29 | const { renderToString } = require("react-dom/server");
30 | import App from "./App";
31 |
32 | const server = express();
33 |
34 | server.use(
35 | createSSRMiddleware((req, res, next) => {
36 | const body = renderToString();
37 | next({ body }, req, res);
38 | })
39 | );
40 |
41 | const PORT = process.env.REACT_APP_SERVER_SIDE_RENDERING_PORT || 8888;
42 | server.listen(PORT, () => {
43 | console.log(`server running on port ${PORT}`);
44 | });
45 | ```
46 |
47 | You can edit the server.js file with your custom code and other middlewares.
48 |
49 | ### Step 3
50 |
51 | `npm start`
52 |
53 | ## Caveats
54 |
55 | It only works with Create React App version 2
56 |
--------------------------------------------------------------------------------
/bin/react-scripts-ssr.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | var spawn = require("cross-spawn");
3 | const args = process.argv.slice(2);
4 |
5 | const scriptIndex = args.findIndex(x => x === "build-server" || x === "start");
6 | const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
7 | const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
8 |
9 | switch (script) {
10 | case "proxy":
11 | case "build-server":
12 | case "start": {
13 | const result = spawn.sync(
14 | "node",
15 | nodeArgs
16 | .concat(require.resolve("../scripts/" + script))
17 | .concat(args.slice(scriptIndex + 1)),
18 | { stdio: "inherit" }
19 | );
20 | if (result.signal) {
21 | if (result.signal === "SIGKILL") {
22 | console.log(
23 | "The build failed because the process exited too early. " +
24 | "This probably means the system ran out of memory or someone called " +
25 | "`kill -9` on the process."
26 | );
27 | } else if (result.signal === "SIGTERM") {
28 | console.log(
29 | "The build failed because the process exited too early. " +
30 | "Someone might have called `kill` or `killall`, or the system could " +
31 | "be shutting down."
32 | );
33 | }
34 | process.exit(1);
35 | }
36 | process.exit(result.status);
37 | break;
38 | }
39 | default:
40 | console.log('Unknown script "' + script + '".');
41 | console.log("Perhaps you need to update react-scripts?");
42 | console.log(
43 | "See: https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#updating-to-new-releases"
44 | );
45 | break;
46 | }
47 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const fs = require("fs");
3 |
4 | const appDirectory = fs.realpathSync(process.cwd());
5 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
6 |
7 | module.exports = {
8 | serverBuild: resolveApp("build-server"),
9 | serverIndexJs: resolveApp("src/server.js"),
10 | customScriptConfig: resolveApp(".react-scripts-ssr.json")
11 | };
12 |
--------------------------------------------------------------------------------
/config/webpack.config.server.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const pathsServer = require('./paths');
3 | const paths = require('react-scripts/config/paths');
4 | const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | const ManifestPlugin = require('webpack-manifest-plugin');
7 |
8 | const resolve = require('resolve');
9 |
10 | const isEnvDevelopment = process.env.NODE_ENV === 'development'
11 | const isEnvProduction = process.env.NODE_ENV === 'production'
12 |
13 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
14 | const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
15 |
16 | const publicPath = isEnvProduction
17 | ? paths.servedPath
18 | : isEnvDevelopment && '/';
19 |
20 | // style files regexes
21 | const cssRegex = /\.css$/;
22 | const cssModuleRegex = /\.module\.css$/;
23 | const sassRegex = /\.(scss|sass)$/;
24 | const sassModuleRegex = /\.module\.(scss|sass)$/;
25 |
26 | const shouldUseRelativeAssetPaths = publicPath === './';
27 |
28 | const getStyleLoaders = (cssOptions, preProcessor) => {
29 | const loaders = [
30 | isEnvDevelopment && require.resolve('style-loader'),
31 | isEnvProduction && {
32 | loader: MiniCssExtractPlugin.loader,
33 | options: Object.assign(
34 | {},
35 | shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
36 | ),
37 | },
38 | {
39 | loader: require.resolve('css-loader'),
40 | options: cssOptions,
41 | },
42 | {
43 | // Options for PostCSS as we reference these options twice
44 | // Adds vendor prefixing based on your specified browser support in
45 | // package.json
46 | loader: require.resolve('postcss-loader'),
47 | options: {
48 | // Necessary for external CSS imports to work
49 | // https://github.com/facebook/create-react-app/issues/2677
50 | ident: 'postcss',
51 | plugins: () => [
52 | require('postcss-flexbugs-fixes'),
53 | require('postcss-preset-env')({
54 | autoprefixer: {
55 | flexbox: 'no-2009',
56 | },
57 | stage: 3,
58 | }),
59 | ],
60 | sourceMap: isEnvProduction && shouldUseSourceMap,
61 | },
62 | },
63 | ].filter(Boolean);
64 | if (preProcessor) {
65 | loaders.push({
66 | loader: require.resolve(preProcessor),
67 | options: {
68 | sourceMap: isEnvProduction && shouldUseSourceMap,
69 | },
70 | });
71 | }
72 | return loaders;
73 | };
74 |
75 | module.exports = {
76 | mode: 'development',
77 | entry: [pathsServer.serverIndexJs],
78 | devtool: 'source-map',
79 | output: {
80 | path: pathsServer.serverBuild,
81 | publicPath: publicPath,
82 | filename: 'index.js',
83 | },
84 | module: {
85 | rules: [
86 | // {
87 | // test: /\.(js|mjs|jsx|ts|tsx)$/,
88 | // include: paths.appSrc,
89 | // loader: require.resolve('babel-loader'),
90 | // options: {
91 | // customize: require.resolve(
92 | // 'babel-preset-react-app/webpack-overrides'
93 | // ),
94 | // // @remove-on-eject-begin
95 | // babelrc: false,
96 | // configFile: false,
97 | // presets: [require.resolve('babel-preset-react-app')],
98 | // // Make sure we have a unique cache identifier, erring on the
99 | // // side of caution.
100 | // plugins: [
101 | // [
102 | // require.resolve('babel-plugin-named-asset-import'),
103 | // {
104 | // loaderMap: {
105 | // svg: {
106 | // ReactComponent:
107 | // '@svgr/webpack?-prettier,-svgo![path]',
108 | // },
109 | // },
110 | // },
111 | // ],
112 | // ],
113 | // // This is a feature of `babel-loader` for webpack (not Babel itself).
114 | // // It enables caching results in ./node_modules/.cache/babel-loader/
115 | // // directory for faster rebuilds.
116 | // cacheDirectory: true,
117 | // cacheCompression: isEnvProduction,
118 | // compact: isEnvProduction,
119 | // },
120 | // },
121 | // // Process any JS outside of the app with Babel.
122 | // // Unlike the application JS, we only compile the standard ES features.
123 | // {
124 | // test: /\.(js|mjs)$/,
125 | // exclude: /@babel(?:\/|\\{1,2})runtime/,
126 | // loader: require.resolve('babel-loader'),
127 | // options: {
128 | // babelrc: false,
129 | // configFile: false,
130 | // compact: false,
131 | // presets: [
132 | // [
133 | // require.resolve('babel-preset-react-app/dependencies'),
134 | // { helpers: true },
135 | // ],
136 | // ],
137 | // cacheDirectory: true,
138 | // cacheCompression: isEnvProduction,
139 | // // If an error happens in a package, it's possible to be
140 | // // because it was compiled. Thus, we don't want the browser
141 | // // debugger to show the original code. Instead, the code
142 | // // being evaluated would be much more helpful.
143 | // sourceMaps: false,
144 | // },
145 | // },
146 | {
147 | test: /\.(js|jsx)$/,
148 | include: paths.appSrc,
149 | exclude: /node_modules/,
150 | //loader: require.resolve('babel-loader'),
151 | use: {
152 | loader: "babel-loader",
153 | options: {
154 | // babelrc: false,
155 | // configFile: false,
156 | //presets: [require.resolve('babel-preset-react-app')],
157 | "presets": ["react-app"],
158 | // Make sure we have a unique cache identifier, erring on the
159 | // side of caution.
160 | // We remove this when the user ejects because the default
161 | // is sane and uses Babel options. Instead of options, we use
162 | // the react-scripts and babel-preset-react-app versions.
163 | // cacheIdentifier: getCacheIdentifier('production', [
164 | // 'babel-plugin-named-asset-import',
165 | // 'babel-preset-react-app',
166 | // 'react-dev-utils',
167 | // 'react-scripts',
168 | // ]),
169 | // @remove-on-eject-end
170 | plugins: [
171 | [
172 | require.resolve('babel-plugin-named-asset-import'),
173 | {
174 | loaderMap: {
175 | svg: {
176 | ReactComponent: '@svgr/webpack?-prettier,-svgo![path]',
177 | },
178 | },
179 | },
180 | ],
181 | ],
182 | //cacheDirectory: true,
183 | // Save disk space when time isn't as important
184 | //cacheCompression: false,
185 | //compact: false,
186 | },
187 | },
188 | },
189 | {
190 | // "oneOf" will traverse all following loaders until one will
191 | // match the requirements. When no loader matches it will fall
192 | // back to the "file" loader at the end of the loader list.
193 | oneOf: [
194 | // "url" loader works like "file" loader except that it embeds assets
195 | // smaller than specified limit in bytes as data URLs to avoid requests.
196 | // A missing `test` is equivalent to a match.
197 | {
198 | test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
199 | loader: require.resolve('url-loader'),
200 | options: {
201 | limit: 10000,
202 | name: 'static/media/[name].[hash:8].[ext]',
203 | },
204 | },
205 | // Process application JS with Babel.
206 | // The preset includes JSX, Flow, TypeScript, and some ESnext features.
207 | {
208 | test: /\.(js|mjs|jsx|ts|tsx)$/,
209 | include: paths.appSrc,
210 | loader: require.resolve('babel-loader'),
211 | options: {
212 | customize: require.resolve(
213 | 'babel-preset-react-app/webpack-overrides'
214 | ),
215 | // @remove-on-eject-begin
216 | babelrc: false,
217 | configFile: false,
218 | presets: [require.resolve('babel-preset-react-app')],
219 | // Make sure we have a unique cache identifier, erring on the
220 | // side of caution.
221 | // We remove this when the user ejects because the default
222 | // is sane and uses Babel options. Instead of options, we use
223 | // the react-scripts and babel-preset-react-app versions.
224 | cacheIdentifier: getCacheIdentifier(
225 | isEnvProduction
226 | ? 'production'
227 | : isEnvDevelopment && 'development',
228 | [
229 | 'babel-plugin-named-asset-import',
230 | 'babel-preset-react-app',
231 | 'react-dev-utils',
232 | 'react-scripts',
233 | ]
234 | ),
235 | // @remove-on-eject-end
236 | plugins: [
237 | [
238 | require.resolve('babel-plugin-named-asset-import'),
239 | {
240 | loaderMap: {
241 | svg: {
242 | ReactComponent:
243 | '@svgr/webpack?-prettier,-svgo![path]',
244 | },
245 | },
246 | },
247 | ],
248 | ],
249 | // This is a feature of `babel-loader` for webpack (not Babel itself).
250 | // It enables caching results in ./node_modules/.cache/babel-loader/
251 | // directory for faster rebuilds.
252 | cacheDirectory: true,
253 | cacheCompression: isEnvProduction,
254 | compact: isEnvProduction,
255 | },
256 | },
257 | // Process any JS outside of the app with Babel.
258 | // Unlike the application JS, we only compile the standard ES features.
259 | {
260 | test: /\.(js|mjs)$/,
261 | exclude: /@babel(?:\/|\\{1,2})runtime/,
262 | loader: require.resolve('babel-loader'),
263 | options: {
264 | babelrc: false,
265 | configFile: false,
266 | compact: false,
267 | presets: [
268 | [
269 | require.resolve('babel-preset-react-app/dependencies'),
270 | { helpers: true },
271 | ],
272 | ],
273 | cacheDirectory: true,
274 | cacheCompression: isEnvProduction,
275 | // If an error happens in a package, it's possible to be
276 | // because it was compiled. Thus, we don't want the browser
277 | // debugger to show the original code. Instead, the code
278 | // being evaluated would be much more helpful.
279 | sourceMaps: false,
280 | },
281 | },
282 | // // "postcss" loader applies autoprefixer to our CSS.
283 | // // "css" loader resolves paths in CSS and adds assets as dependencies.
284 | // // "style" loader turns CSS into JS modules that inject