├── src
├── components
│ ├── index.js
│ └── app.jsx
├── server.js
└── routes
│ └── ssr.js
├── .babelrc
├── README.md
├── webpack.config.js
├── package.json
└── .gitignore
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { hydrate } from "react-dom";
3 | import App from "./app";
4 |
5 | hydrate(, document.getElementById("reactele"));
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", { "targets": { "node": "current" } }],
4 | "@babel/preset-react"
5 | ],
6 | "plugins": ["@babel/plugin-proposal-class-properties"]
7 | }
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Server Side Rendering
2 |
3 | Sample code to demonstrate React Server Side Rendering.
4 |
5 | For the tutorial click [here]( https://medium.com/@danlegion/react-server-side-rendering-with-express-b6faf56ce22)
6 |
7 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import compression from "compression";
3 | import ssr from "./routes/ssr";
4 | const app = express();
5 |
6 | app.use(compression());
7 | app.use(express.static("public"));
8 |
9 | app.use("/firstssr", ssr);
10 |
11 | const port = process.env.PORT || 3030;
12 | app.listen(port, function listenHandler() {
13 | console.info(`Running on ${port}...`);
14 | });
15 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const config = {
4 | entry: {
5 | vendor: ["@babel/polyfill", "react"],
6 | app: ["./src/components/index.js"]
7 | },
8 | output: {
9 | path: path.resolve(__dirname, "public"),
10 | filename: "[name].js"
11 | },
12 | module: {
13 | rules: [
14 | {
15 | test: /\.(js|jsx)$/,
16 | use: {
17 | loader: "babel-loader",
18 | options: {
19 | presets: ["@babel/preset-env", "@babel/preset-react"]
20 | }
21 | },
22 | exclude: /node_modules/
23 | }
24 | ]
25 | },
26 | resolve: {
27 | extensions: [".js", ".jsx", ".json", ".wasm", ".mjs", "*"]
28 | }
29 | };
30 |
31 | module.exports = config;
32 |
--------------------------------------------------------------------------------
/src/routes/ssr.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import App from "../components/app";
3 | import React from "react";
4 | import { renderToString } from "react-dom/server";
5 | import hbs from "handlebars";
6 |
7 | const router = express.Router();
8 |
9 | router.get("/", async (req, res) => {
10 | const theHtml = `
11 |
12 |
My First SSR
13 |
14 | My First Server Side Render
15 | {{{reactele}}}
16 |
17 |
18 |
19 |
20 | `;
21 |
22 | const hbsTemplate = hbs.compile(theHtml);
23 | const reactComp = renderToString();
24 | const htmlToSend = hbsTemplate({ reactele: reactComp });
25 | res.send(htmlToSend);
26 | });
27 |
28 | export default router;
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-ssr",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "dev": "nodemon --exec babel-node src/server.js",
8 | "webpack": "webpack -wd"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "compression": "^1.7.4",
14 | "express": "^4.17.1",
15 | "handlebars": "^4.7.6",
16 | "react": "^16.13.1",
17 | "react-dom": "^16.13.1"
18 | },
19 | "devDependencies": {
20 | "@babel/cli": "^7.10.5",
21 | "@babel/core": "^7.10.5",
22 | "@babel/node": "^7.10.5",
23 | "@babel/plugin-proposal-class-properties": "^7.10.4",
24 | "@babel/plugin-transform-runtime": "^7.10.5",
25 | "@babel/polyfill": "^7.10.4",
26 | "@babel/preset-env": "^7.10.4",
27 | "@babel/preset-react": "^7.10.4",
28 | "babel-loader": "^8.1.0",
29 | "webpack": "^4.43.0",
30 | "webpack-cli": "^3.3.12"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/app.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | class App extends React.Component {
4 | constructor() {
5 | super();
6 | this.handleButtonClick = this.handleButtonClick.bind(this);
7 | this.handleTextChange = this.handleTextChange.bind(this);
8 | this.handleReset = this.handleReset.bind(this);
9 |
10 | this.state = {
11 | name: "",
12 | msg: ""
13 | };
14 | }
15 |
16 | //Handlers
17 | handleButtonClick = e => {
18 | const nameLen = this.state.name.length;
19 | if (nameLen > 0) {
20 | this.setState({
21 | msg: `You name has ${nameLen} characters including space`
22 | });
23 | }
24 | };
25 |
26 | handleTextChange = e => {
27 | this.setState({ name: e.target.value });
28 | };
29 |
30 | handleReset = () => {
31 | this.setState({ name: "", msg: "" });
32 | };
33 | //End Handlers
34 |
35 | render() {
36 | let msg;
37 |
38 | if (this.state.msg !== "") {
39 | msg = {this.state.msg}
;
40 | } else {
41 | msg = "";
42 | }
43 | return (
44 | //do something here where there is a button that will replace the text
45 |
46 |
47 |
54 |
57 |
60 |
61 | {msg}
62 |
63 | );
64 | }
65 | }
66 | export default App;
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_STORE
2 | node_modules
3 | scripts/flow/*/.flowconfig
4 | *~
5 | *.pyc
6 | .grunt
7 | _SpecRunner.html
8 | __benchmarks__
9 | build/
10 | remote-repo/
11 | coverage/
12 | .module-cache
13 | fixtures/dom/public/react-dom.js
14 | fixtures/dom/public/react.js
15 | test/the-files-to-test.generated.js
16 | *.log*
17 | chrome-user-data
18 | *.sublime-project
19 | *.sublime-workspace
20 | .idea
21 | *.iml
22 | .vscode
23 | *.swp
24 | *.swo
25 |
26 | # Logs
27 | logs
28 | *.log
29 | npm-debug.log*
30 | yarn-debug.log*
31 | yarn-error.log*
32 |
33 | # Diagnostic reports (https://nodejs.org/api/report.html)
34 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
35 |
36 | # Runtime data
37 | pids
38 | *.pid
39 | *.seed
40 | *.pid.lock
41 |
42 | # Directory for instrumented libs generated by jscoverage/JSCover
43 | lib-cov
44 |
45 | # Coverage directory used by tools like istanbul
46 | coverage
47 |
48 | # nyc test coverage
49 | .nyc_output
50 |
51 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
52 | .grunt
53 |
54 | # Bower dependency directory (https://bower.io/)
55 | bower_components
56 |
57 | # node-waf configuration
58 | .lock-wscript
59 |
60 | # Compiled binary addons (https://nodejs.org/api/addons.html)
61 | build/Release
62 |
63 | # Dependency directories
64 | node_modules/
65 | jspm_packages/
66 |
67 | # TypeScript v1 declaration files
68 | typings/
69 |
70 | # Optional npm cache directory
71 | .npm
72 |
73 | # Optional eslint cache
74 | .eslintcache
75 |
76 | # Optional REPL history
77 | .node_repl_history
78 |
79 | # Output of 'npm pack'
80 | *.tgz
81 |
82 | # Yarn Integrity file
83 | .yarn-integrity
84 |
85 | # dotenv environment variables file
86 | .env
87 | .env.test
88 |
89 | # parcel-bundler cache (https://parceljs.org/)
90 | .cache
91 |
92 | # next.js build output
93 | .next
94 |
95 | # nuxt.js build output
96 | .nuxt
97 |
98 | # vuepress build output
99 | .vuepress/dist
100 |
101 | # Serverless directories
102 | .serverless/
103 |
104 | #public
105 | public/
106 |
107 | # FuseBox cache
108 | .fusebox/
109 |
110 | # DynamoDB Local files
111 | .dynamodb/
112 |
--------------------------------------------------------------------------------