├── .gitignore ├── sample.secrets.json ├── readme.md ├── index.js ├── layouts └── default.js ├── components ├── navbar.js └── meta.js ├── binaryMimeTypes.js ├── package.json ├── server.js ├── serverless.yml ├── pages ├── index.js └── dogs │ ├── index.js │ └── _breed.js └── LICENCE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | secrets.json 4 | .vscode 5 | .serverless -------------------------------------------------------------------------------- /sample.secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "NODE_ENV": "production", 3 | "DOMAIN": "reactssr.yourdomain.com" 4 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Serverless-Side Rendering React Next 2 | Sample repo for setting up Next and React on AWS Lambda with the Serverless Framework. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const sls = require('serverless-http') 2 | const binaryMimeTypes = require('./binaryMimeTypes') 3 | 4 | const server = require('./server') 5 | module.exports.server = sls(server, { 6 | binary: binaryMimeTypes 7 | }) 8 | -------------------------------------------------------------------------------- /layouts/default.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Meta from '../components/meta' 3 | import Navbar from '../components/navbar' 4 | export default ({ children, meta }) => ( 5 |
6 | 7 | 8 | { children } 9 |
10 | ) 11 | -------------------------------------------------------------------------------- /components/navbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'next/link' 3 | 4 | export default () => ( 5 | 18 | ) 19 | -------------------------------------------------------------------------------- /binaryMimeTypes.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'application/javascript', 3 | 'application/json', 4 | 'application/octet-stream', 5 | 'application/xml', 6 | 'font/eot', 7 | 'font/opentype', 8 | 'font/otf', 9 | 'image/jpeg', 10 | 'image/png', 11 | 'image/svg+xml', 12 | 'text/comma-separated-values', 13 | 'text/css', 14 | 'text/html', 15 | 'text/javascript', 16 | 'text/plain', 17 | 'text/text', 18 | 'text/xml' 19 | ] 20 | -------------------------------------------------------------------------------- /components/meta.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | export default ({ props = { title, description } }) => ( 3 |
4 | 5 | { props.title || 'Next.js Test Title' } 6 | 7 | 8 | 9 | 10 |
11 | ) 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-side-rendering-react-next", 3 | "version": "0.0.1", 4 | "description": "Sample repo for setting up Next and React on AWS Lambda with the Serverless Framework.", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "next build", 8 | "deploy": "next build && sls deploy" 9 | }, 10 | "keywords": [], 11 | "author": "Adnan Rahić", 12 | "license": "MIT", 13 | "dependencies": { 14 | "axios": "^0.18.1", 15 | "express": "^4.16.4", 16 | "next": "^7.0.2", 17 | "path-match": "^1.2.4", 18 | "react": "^16.6.3", 19 | "react-dom": "^16.6.3", 20 | "serverless-apigw-binary": "^0.4.4", 21 | "serverless-http": "^1.6.0", 22 | "url": "^0.11.0", 23 | "serverless-domain-manager": "^2.6.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const dev = process.env.NODE_ENV !== 'production' 4 | const next = require('next') 5 | const pathMatch = require('path-match') 6 | const app = next({ dev }) 7 | const handle = app.getRequestHandler() 8 | const { parse } = require('url') 9 | 10 | const server = express() 11 | const route = pathMatch() 12 | server.use('/_next', express.static(path.join(__dirname, '.next'))) 13 | server.get('/', (req, res) => app.render(req, res, '/')) 14 | server.get('/dogs', (req, res) => app.render(req, res, '/dogs')) 15 | server.get('/dogs/:breed', (req, res) => { 16 | const params = route('/dogs/:breed')(parse(req.url).pathname) 17 | return app.render(req, res, '/dogs/_breed', params) 18 | }) 19 | server.get('*', (req, res) => handle(req, res)) 20 | 21 | module.exports = server 22 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: ssr-react-next 2 | 3 | provider: 4 | name: aws 5 | runtime: nodejs12.x 6 | stage: ${self:custom.secrets.NODE_ENV} 7 | region: us-east-1 8 | environment: 9 | NODE_ENV: ${self:custom.secrets.NODE_ENV} 10 | 11 | functions: 12 | server: 13 | handler: index.server 14 | events: 15 | - http: ANY / 16 | - http: ANY /{proxy+} 17 | 18 | plugins: 19 | - serverless-apigw-binary 20 | - serverless-domain-manager 21 | 22 | custom: 23 | secrets: ${file(secrets.json)} 24 | apigwBinary: 25 | types: 26 | - '*/*' 27 | customDomain: 28 | domainName: ${self:custom.secrets.DOMAIN} 29 | basePath: '' 30 | stage: ${self:custom.secrets.NODE_ENV} 31 | createRoute53Record: true 32 | # endpointType: 'regional' 33 | # if the ACM certificate is created in a region except for `'us-east-1'` you need `endpointType: 'regional'` 34 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Default from '../layouts/default' 3 | import axios from 'axios' 4 | const meta = { title: 'Index title', description: 'Index description' } 5 | 6 | class IndexPage extends React.Component { 7 | constructor (props) { 8 | super(props) 9 | this.state = { 10 | loading: true, 11 | dog: {} 12 | } 13 | this.fetchData = this.fetchData.bind(this) 14 | } 15 | async componentDidMount () { 16 | await this.fetchData() 17 | } 18 | async fetchData () { 19 | this.setState({ loading: true }) 20 | const { data } = await axios.get( 21 | 'https://api.thedogapi.com/v1/images/search?limit=1' 22 | ) 23 | this.setState({ 24 | dog: data[0], 25 | loading: false 26 | }) 27 | } 28 | render () { 29 | return ( 30 | 31 |
32 |

This is the Front Page.

33 |

Random dog of the day:

34 | 35 |
36 |
37 | ) 38 | } 39 | } 40 | 41 | export default IndexPage 42 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Belong 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 | -------------------------------------------------------------------------------- /pages/dogs/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import axios from 'axios' 3 | import Default from '../../layouts/default' 4 | const meta = { title: 'Dogs title', description: 'Dogs description' } 5 | 6 | class DogsPage extends React.Component { 7 | constructor (props) { 8 | super(props) 9 | this.state = { 10 | loading: true, 11 | dogs: [] 12 | } 13 | this.fetchData = this.fetchData.bind(this) 14 | } 15 | async componentDidMount () { 16 | await this.fetchData() 17 | } 18 | async fetchData () { 19 | this.setState({ loading: true }) 20 | const { data } = await axios.get( 21 | 'https://api.thedogapi.com/v1/images/search?size=thumb&limit=10' 22 | ) 23 | this.setState({ 24 | dogs: data, 25 | loading: false 26 | }) 27 | } 28 | renderDogList () { 29 | return ( 30 | 37 | ) 38 | } 39 | render () { 40 | return ( 41 | 42 |
43 |

Here you have all dogs.

44 | {this.renderDogList()} 45 |
46 |
47 | ) 48 | } 49 | } 50 | 51 | export default DogsPage 52 | -------------------------------------------------------------------------------- /pages/dogs/_breed.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import axios from 'axios' 3 | import Default from '../../layouts/default' 4 | 5 | class DogBreedPage extends React.Component { 6 | static getInitialProps ({ query: { breed } }) { 7 | return { breed } 8 | } 9 | constructor (props) { 10 | super(props) 11 | this.state = { 12 | loading: true, 13 | meta: {}, 14 | dogs: [] 15 | } 16 | this.fetchData = this.fetchData.bind(this) 17 | } 18 | async componentDidMount () { 19 | await this.fetchData() 20 | } 21 | async fetchData () { 22 | this.setState({ loading: true }) 23 | const reg = new RegExp(this.props.breed, 'g') 24 | 25 | const { data } = await axios.get( 26 | 'https://api.thedogapi.com/v1/images/search?size=thumb&has_breeds=true&limit=50' 27 | ) 28 | const filteredDogs = data.filter(dog => 29 | dog.breeds[0] 30 | .name 31 | .toLowerCase() 32 | .match(reg) 33 | ) 34 | this.setState({ 35 | dogs: filteredDogs, 36 | breed: this.props.breed, 37 | meta: { title: `Only ${this.props.breed} here!`, description: 'Cute doggies. :D' }, 38 | loading: false 39 | }) 40 | } 41 | renderDogList () { 42 | return ( 43 | 50 | ) 51 | } 52 | render () { 53 | return ( 54 | 55 |
56 |

Dog breed: {this.props.breed}

57 | {this.renderDogList()} 58 |
59 |
60 | ) 61 | } 62 | } 63 | 64 | export default DogBreedPage 65 | --------------------------------------------------------------------------------