├── .gitignore ├── Browser ├── bundle.css ├── bundle.js └── bundle.map ├── app.js ├── handler.js ├── handler.map ├── index.html ├── package-lock.json ├── package.json ├── parcel.js ├── readme.md ├── serverless.yml └── src ├── App.js ├── app.css ├── index.js └── users.js /.gitignore: -------------------------------------------------------------------------------- 1 | .serverless 2 | .cache 3 | node_modules 4 | -------------------------------------------------------------------------------- /Browser/bundle.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | h1{ 4 | text-align: center; 5 | color: red; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import serverless from "serverless-http"; 2 | import express from "express"; 3 | import cors from "cors"; 4 | import bodyParser from "body-parser"; 5 | import React from "react"; 6 | import { renderToString } from "react-dom/server"; 7 | import App from "./src/App"; 8 | import Data from "./src/users"; 9 | import fs from "fs"; 10 | import path from "path"; 11 | 12 | 13 | 14 | const app = express(); 15 | 16 | app.use(cors()); 17 | app.use(bodyParser.json()); 18 | app.use(bodyParser.urlencoded({ extended: false })); 19 | app.use(express.static(path.resolve(__dirname, "./Browser"))); 20 | 21 | 22 | const markup = fs.readFileSync(__dirname + "/index.html", 23 | "utf8" 24 | ); 25 | 26 | app.get("**", (req, res) => { 27 | Data().then(users => { 28 | const html = renderToString(); 29 | res.send(markup.replace("", html)); 30 | }); 31 | }); 32 | 33 | module.exports.ssr = serverless(app); 34 | -------------------------------------------------------------------------------- /handler.js: -------------------------------------------------------------------------------- 1 | // modules are defined as an array 2 | // [ module function, map of requires ] 3 | // 4 | // map of requires is short require name -> numeric require 5 | // 6 | // anything defined in a previous bundle is accessed via the 7 | // orig method which is the require for previous bundles 8 | 9 | // eslint-disable-next-line no-global-assign 10 | parcelRequire = (function (modules, cache, entry, globalName) { 11 | // Save the require from previous bundle to this closure if any 12 | var previousRequire = typeof parcelRequire === 'function' && parcelRequire; 13 | var nodeRequire = typeof require === 'function' && require; 14 | 15 | function newRequire(name, jumped) { 16 | if (!cache[name]) { 17 | if (!modules[name]) { 18 | // if we cannot find the module within our internal map or 19 | // cache jump to the current global require ie. the last bundle 20 | // that was added to the page. 21 | var currentRequire = typeof parcelRequire === 'function' && parcelRequire; 22 | if (!jumped && currentRequire) { 23 | return currentRequire(name, true); 24 | } 25 | 26 | // If there are other bundles on this page the require from the 27 | // previous one is saved to 'previousRequire'. Repeat this as 28 | // many times as there are bundles until the module is found or 29 | // we exhaust the require chain. 30 | if (previousRequire) { 31 | return previousRequire(name, true); 32 | } 33 | 34 | // Try the node require function if it exists. 35 | if (nodeRequire && typeof name === 'string') { 36 | return nodeRequire(name); 37 | } 38 | 39 | var err = new Error('Cannot find module \'' + name + '\''); 40 | err.code = 'MODULE_NOT_FOUND'; 41 | throw err; 42 | } 43 | 44 | localRequire.resolve = resolve; 45 | 46 | var module = cache[name] = new newRequire.Module(name); 47 | 48 | modules[name][0].call(module.exports, localRequire, module, module.exports, this); 49 | } 50 | 51 | return cache[name].exports; 52 | 53 | function localRequire(x){ 54 | return newRequire(localRequire.resolve(x)); 55 | } 56 | 57 | function resolve(x){ 58 | return modules[name][1][x] || x; 59 | } 60 | } 61 | 62 | function Module(moduleName) { 63 | this.id = moduleName; 64 | this.bundle = newRequire; 65 | this.exports = {}; 66 | } 67 | 68 | newRequire.isParcelRequire = true; 69 | newRequire.Module = Module; 70 | newRequire.modules = modules; 71 | newRequire.cache = cache; 72 | newRequire.parent = previousRequire; 73 | 74 | for (var i = 0; i < entry.length; i++) { 75 | newRequire(entry[i]); 76 | } 77 | 78 | if (entry.length) { 79 | // Expose entry point to Node, AMD or browser globals 80 | // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js 81 | var mainExports = newRequire(entry[entry.length - 1]); 82 | 83 | // CommonJS 84 | if (typeof exports === "object" && typeof module !== "undefined") { 85 | module.exports = mainExports; 86 | 87 | // RequireJS 88 | } else if (typeof define === "function" && define.amd) { 89 | define(function () { 90 | return mainExports; 91 | }); 92 | 93 | // 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssr-react", 3 | "version": "1.0.0", 4 | "description": "ssr rendering react using serverless", 5 | "main": "index.js", 6 | "scripts": { 7 | "bundle": "rimraf Browser && node parcel.js", 8 | "start": "sls offline start", 9 | "deploy": "sls deploy" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "babel": { 14 | "presets": [ 15 | [ 16 | "env", 17 | { 18 | "targets": { 19 | "browsers": [ 20 | ">1%", 21 | "last 3 versions" 22 | ] 23 | } 24 | } 25 | ], 26 | "stage-2", 27 | "latest", 28 | "react" 29 | ], 30 | "plugins": [ 31 | "syntax-dynamic-import", 32 | "transform-class-properties" 33 | ] 34 | }, 35 | "devDependencies": { 36 | "babel": "^6.23.0", 37 | "babel-cli": "^6.26.0", 38 | "babel-core": "^6.26.3", 39 | "babel-loader": "^7.1.4", 40 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 41 | "babel-plugin-transform-class-properties": "^6.24.1", 42 | "babel-preset-env": "^1.7.0", 43 | "babel-preset-latest": "^6.24.1", 44 | "babel-preset-react": "^6.24.1", 45 | "babel-preset-stage-2": "^6.24.1", 46 | "parcel-bundler": "^1.8.1", 47 | "rimraf": "^2.6.2", 48 | "serverless-offline": "^3.25.4" 49 | }, 50 | "dependencies": { 51 | "body-parser": "^1.18.3", 52 | "cors": "^2.8.4", 53 | "express": "^4.16.3", 54 | "isomorphic-fetch": "^2.2.1", 55 | "react": "^16.4.0", 56 | "react-dom": "^16.4.0", 57 | "react-router-dom": "^4.2.2", 58 | "serverless-http": "^1.5.5" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /parcel.js: -------------------------------------------------------------------------------- 1 | const Bundler = require("parcel-bundler"); 2 | const Path = require("path"); 3 | 4 | // Entrypoint file location 5 | const server = Path.join(__dirname, "app.js"); 6 | 7 | // Bundler options 8 | const serverOpt = { 9 | outDir: "./", // The out directory to put the build files in, defaults to dist 10 | outFile: "handler.js", // The name of the outputFile 11 | publicUrl: "./", // The url to server on, defaults to dist 12 | watch: true, 13 | cacheDir: ".cache", // The directory cache gets put in, defaults to .cache 14 | minify: false, // Minify files, enabled if process.env.NODE_ENV === 'production' 15 | target: "node", // browser/node/electron, defaults to browser 16 | logLevel: 3, // 3 = log everything, 2 = log warnings & errors, 1 = log errors 17 | sourceMaps: true, // Enable or disable sourcemaps, defaults to enabled (not supported in minified builds yet) 18 | detailedReport: true // Prints a detailed report of the bundles, assets, filesizes and times, defaults to false, reports are only printed if watch is disabled 19 | }; 20 | 21 | // Entrypoint file location 22 | const browser = Path.join(__dirname, "./src/index.js"); 23 | 24 | // Bundler options 25 | const browserOpt = { 26 | outDir: "./Browser", 27 | outFile: "bundle.js", 28 | publicUrl: "./", 29 | watch: true, 30 | cacheDir: ".cache", 31 | minify: false, 32 | target: "browser", 33 | https: false, 34 | logLevel: 3, 35 | hmrPort: 0, 36 | sourceMaps: true, 37 | hmrHostname: "", 38 | detailedReport: false 39 | }; 40 | 41 | const bundler = new Bundler(server, serverOpt); 42 | const bundle = bundler.bundle(); 43 | 44 | // Initialises a bundler using the entrypoint location and options provided 45 | const bundlers = new Bundler(browser, browserOpt); 46 | 47 | // Run the bundler, this returns the main bundle 48 | // Use the events if you're using watch mode as this promise will only trigger once and not for every rebuild 49 | const bundle1 = bundlers.bundle(); 50 | 51 | // Run the bundler, this returns the main bundle 52 | // Use the events if you're using watch mode as this promise will only trigger once and not for every rebuild 53 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Serverless Server Side Rendering With React 2 | 3 | ### Requirements 4 | - Aws Account 5 | - Serverless Framework cli 6 | - [Get Aws Credentials](https://www.youtube.com/watch?v=tgb_MRVylWw) 7 | 8 | 9 | 10 | ![Server Side Rendering With React](https://res.cloudinary.com/practicaldev/image/fetch/s--PUxef0xy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/u6wnnm9bu0edwiqekjaj.gif) -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | 2 | service: ssr 3 | 4 | provider: 5 | name: aws 6 | runtime: nodejs8.10 7 | 8 | 9 | functions: 10 | app: 11 | handler: handler.ssr 12 | events: 13 | - http: ANY / 14 | - http: 'ANY {proxy+}' 15 | 16 | plugins: 17 | - serverless-offline -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React,{Fragment} from "react"; 2 | 3 | const App = (props) => { 4 | return ( 5 | 6 |

Users

7 | 12 |
13 | ) 14 | } 15 | 16 | export default App; -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | h1{ 4 | text-align: center; 5 | color: red; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./app.css"; 3 | import { hydrate } from "react-dom"; 4 | 5 | import App from "./App"; 6 | import Data from "./users"; 7 | 8 | Data().then(users => { 9 | hydrate(, document.getElementById("root")); 10 | }); 11 | -------------------------------------------------------------------------------- /src/users.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import fetch from 'isomorphic-fetch'; 3 | 4 | function Data() { 5 | return fetch('https://jsonplaceholder.typicode.com/users') 6 | .then(data => data.json()) 7 | } 8 | export default Data; --------------------------------------------------------------------------------