├── .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 |
6 |
7 |
8 | Home
9 |
10 |
11 | Dogs
12 |
13 |
14 | Only Shepherds
15 |
16 |
17 |
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 |
31 | {this.state.dogs.map((dog, key) =>
32 |
33 |
34 |
35 | )}
36 |
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 |
44 | {this.state.dogs.map((dog, key) =>
45 |
46 |
47 |
48 | )}
49 |
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 |
--------------------------------------------------------------------------------