├── .babelrc ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── components │ ├── app.jsx │ └── index.js ├── routes │ └── ssr.js └── server.js └── webpack.config.js /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------