DEV Community

Cover image for A crash course on serverless-side rendering with React.js, Next.js and AWS Lambda
Adnan Rahić
Adnan Rahić

Posted on • Updated on

A crash course on serverless-side rendering with React.js, Next.js and AWS Lambda

Not so long ago I started exploring server-side rendered single-page applications. Yeah, try saying that three times fast. Building products for startups has taught me SEO is a must if you want an online presence. But, you also want the performance SPAs can provide.

We want the best of both worlds. The SEO boost server-side rendering provides, and the speed of a Single Page Application. Today I'll show you all this while hosting it basically for free in a serverless environment on AWS Lambda.

TL;DR

Let's run through what this tutorial will cover. You can skim through and jump to the section that interest you. Or, be a nerd and keep reading. * whisper * Please be a nerd.

Note: The code we will write is already on GitHub if you need further reference or miss any steps, feel free to check it out. The guys over at Cube.js gave me a quick rundown of React before I started writing this tutorial. They have a serverless analytics framework that plugs nicely into React. Feel free to give it a try.

What're we building?

Well, a blazing-fast React application of course! The cost of every SPA is lousy SEO capabilities though. So we need to build the app in a way to incorporate server-side rendering. Sounds simple enough. We can use Next.js, a lightweight framework for static and server-rendered React.js applications.

To accomplish this we need to spin up a simple Express server and configure the Next app to serve files through Express. It is way simpler than it sounds.

However, from the title you can assume we don't like the word server in my neighborhood. The solution is to deploy this whole application to AWS Lambda! It is a tiny Node.js instance after all.

Ready? Let's get crackin'!

Configure and install dependencies

As always, we're starting with the boring part, setting up the project and installing dependencies.

1. Install the Serverless Framework

In order for serverless development to not be absolute torture, go ahead and install the Serverless framework.

$ npm i -g serverless
Enter fullscreen mode Exit fullscreen mode

Note: If you’re using Linux or Mac, you may need to run the command as sudo.

Once installed globally on your machine, the commands will be available to you from wherever in the terminal. But for it to communicate with your AWS account you need to configure an IAM User. Jump over here for the explanation, then come back and run the command below, with the provided keys.

$ serverless config credentials \ 
    --provider aws \ 
    --key xxxxxxxxxxxxxx \ 
    --secret xxxxxxxxxxxxxx
Enter fullscreen mode Exit fullscreen mode

Now your Serverless installation knows what account to connect to when you run any terminal command. Let’s jump in and see it in action.

2. Create a service

Create a new directory to house your Serverless application services. Fire up a terminal in there. Now you’re ready to create a new service.

What’s a service you ask? View it like a project. But not really. It’s where you define AWS Lambda functions, the events that trigger them and any AWS infrastructure resources they require, all in a file called serverless.yml.

Back in your terminal type:

$ serverless create --template aws-nodejs --path ssr-react-next
Enter fullscreen mode Exit fullscreen mode

The create command will create a new service. Shocker! But here’s the fun part. We need to pick a runtime for the function. This is called the template. Passing in aws-nodejs will set the runtime to Node.js. Just what we want. The path will create a folder for the service.

3. Install npm modules

Change into the ssr-react-next folder in your terminal. There should be three files in there, but for now, let's first initialize npm.

$ npm init -y
Enter fullscreen mode Exit fullscreen mode

After the package.json file is created, you can install a few dependencies.

$ npm i \
    axios \
    express \
    serverless-http \
    serverless-apigw-binary \
    next \
    react \
    react-dom \
    path-match \
    url \
    serverless-domain-manager
Enter fullscreen mode Exit fullscreen mode

These are our production dependencies, and I'll go into more detail explaining what they do a bit further down. The last one, called serverless-domain-manager will let us tie a domain to our endpoints. Sweet!

Now, your package.json should look something like this.

// package.json
{
  "name": "serverless-side-rendering-react-next",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": { // ADD THESE SCRIPTS
    "build": "next build",
    "deploy": "next build && sls deploy"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.18.0",
    "express": "^4.16.4",
    "next": "^7.0.2",
    "path-match": "^1.2.4",
    "react": "^16.6.3",
    "react-dom": "^16.6.3",
    "serverless-apigw-binary": "^0.4.4",
    "serverless-http": "^1.6.0",
    "url": "^0.11.0",
    "serverless-domain-manager": "^2.6.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

We also need to add two scripts, one for building and one for deploying the app. You can see them in the scripts section of the package.json.

4. Configure the serverless.yml file

Moving on, let's finally open up the project in a code editor. Check out the serverless.yml file, it contains all the configuration settings for this service. Here you specify both general configuration settings and per function settings. Your serverless.yml will be full of boilerplate code and comments. Feel free to delete it all and paste this in.

service: ssr-react-next

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${self:custom.secrets.NODE_ENV}
  region: us-east-1
  environment: 
    NODE_ENV: ${self:custom.secrets.NODE_ENV}

functions:
  server:
    handler: index.server
    events:
      - http: ANY /
      - http: ANY /{proxy+}

plugins:
  - serverless-apigw-binary
  - serverless-domain-manager

custom:
  secrets: ${file(secrets.json)}
  apigwBinary:
    types:
      - '*/*'
  customDomain:
    domainName: ${self:custom.secrets.DOMAIN}
    basePath: ''
    stage: ${self:custom.secrets.NODE_ENV}
    createRoute53Record: true
    # endpointType: 'regional'
    # if the ACM certificate is created in a region except for `'us-east-1'` you need `endpointType: 'regional'`
Enter fullscreen mode Exit fullscreen mode

The functions property lists all the functions in the service. We will only need one function because it will run the Next app and render the React pages. It works by spinning up a tiny Express server, running the Next renderer alongside the Express router and passing the server to the serverless-http module.

In turn, this will bundle the whole Express app into a single lambda function and tie it to an API Gateway endpoint. Under the functions property, you can see a server function that will have a handler named server in the index.js file. API Gateway will proxy any and every request to the internal Express router which will then tell Next to render our React.js pages. Woah, that sounds complicated! But it's really not. Once we start writing the code you'll see how simple it really is.

We've also added two plugins, the serverless-apigw-binary for letting more mime types pass through API Gateway and the serverless-domain-manager which lets us hook up domain names to our endpoints effortlessly.

We also have a custom section at the bottom. The secrets property acts as a way to safely load environment variables into our service. They're later referenced by using ${self:custom.secrets.<environment_var>} where the actual values are kept in a simple file called secrets.json.

Apart from that, we're also letting the API Gateway binary plugin know we want to let all types through, and setting a custom domain for our endpoint.

That's it for the configuration, let's add the secrets.json file.

5. Add the secrets file

Add a secrets.json file and paste this in. This will keep us from pushing secret keys to GitHub.

{
  "NODE_ENV": "production",
  "DOMAIN": "react-ssr.your-domain.com"
}
Enter fullscreen mode Exit fullscreen mode

Now, only by changing these values you can deploy different environments to different stages and domains. Pretty cool.

Build the app with the Serverless Framework and Next.js

To build a server-side rendered React.js app we'll use the Next.js framework. It lets you focus on writing the app instead of worrying about SEO. It works by rendering the JavaScript before sending it to the client. Once it's loaded on the client side, it'll cache it and serve it from there instead. You have to love the speed of it!

Let's start by writing the Next.js setup on the server.

1. Setting up the Next.js server(less)-side rendering

Create a file named server.js. Really intuitive, I know.

// server.js
const express = require('express')
const path = require('path')
const dev = process.env.NODE_ENV !== 'production'
const next = require('next')
const pathMatch = require('path-match')
const app = next({ dev })
const handle = app.getRequestHandler()
const { parse } = require('url')

const server = express()
const route = pathMatch()
server.use('/_next', express.static(path.join(__dirname, '.next')))
server.get('/', (req, res) => app.render(req, res, '/'))
server.get('/dogs', (req, res) => app.render(req, res, '/dogs'))
server.get('/dogs/:breed', (req, res) => {
  const params = route('/dogs/:breed')(parse(req.url).pathname)
  return app.render(req, res, '/dogs/_breed', params)
})
server.get('*', (req, res) => handle(req, res))

module.exports = server
Enter fullscreen mode Exit fullscreen mode

It's pretty simple. We're grabbing Express and Next, creating a static route with express.static and passing it the directory of the bundled JavaScript that Next will create. The path is /_next, and it points to the .next folder.

We'll also set up the server-side routes and add a catch-all route for the client-side renderer.

Now, the app needs to be hooked up to serverless-http and exported as a lambda function. Create an index.js file and paste this in.

// index.js
const sls = require('serverless-http')
const binaryMimeTypes = require('./binaryMimeTypes')

const server = require('./server')
module.exports.server = sls(server, {
  binary: binaryMimeTypes
})
Enter fullscreen mode Exit fullscreen mode

As you can see we also need to create binaryMimeTypes.js file to hold all the mime types we want to enable. It'll just a simple array which we pass into the serverless-http module.

// binaryMimeTypes.js
module.exports = [
  'application/javascript',
  'application/json',
  'application/octet-stream',
  'application/xml',
  'font/eot',
  'font/opentype',
  'font/otf',
  'image/jpeg',
  'image/png',
  'image/svg+xml',
  'text/comma-separated-values',
  'text/css',
  'text/html',
  'text/javascript',
  'text/plain',
  'text/text',
  'text/xml'
]
Enter fullscreen mode Exit fullscreen mode

Sweet, that's it regarding the Next.js setup. Let's jump into the client-side code!

2. Writing client-side React.js

In the root of your project create three folders named, components, layouts, pages. Once inside the layouts folder, create a new file with the name default.js, and paste this in.

// layouts/default.js
import React from 'react'
import Meta from '../components/meta'
import Navbar from '../components/navbar'
export default ({ children, meta }) => (
  <div>
    <Meta props={meta} />
    <Navbar />
    { children }
  </div>
)
Enter fullscreen mode Exit fullscreen mode

The default view will have a <Meta /> component for setting the metatags dynamically and a <Navbar/> component. The { children } will be rendered from the component that uses this layout.

Now add two more files. A navbar.js and a meta.js file in the components folder.

// components/navbar.js
import React from 'react'
import Link from 'next/link'

export default () => (
  <nav className='nav'>
    <ul>
      <li>
        <Link href='/'>Home</Link>
      </li>
      <li>
        <Link href='/dogs'>Dogs</Link>
      </li>
      <li>
        <Link href='/dogs/shepherd'>Only Shepherds</Link>
      </li>
    </ul>
  </nav>
)
Enter fullscreen mode Exit fullscreen mode

This is an incredibly simple navigation that'll be used to navigate between some cute dogs. It'll make sense once we add something to the pages folder.

// components/meta.js
import Head from 'next/head'
export default ({ props = { title, description } }) => (
  <div>
    <Head>
      <title>{ props.title || 'Next.js Test Title' }</title>
      <meta name='description' content={props.description || 'Next.js Test Description'} />
      <meta name='viewport' content='width=device-width, initial-scale=1' />
      <meta charSet='utf-8' />
    </Head>
  </div>
)
Enter fullscreen mode Exit fullscreen mode

The meta.js will make it easier for us to inject values into our meta tags. Now you can go ahead and create an index.js file in the pages folder. Paste in the code below.

// pages/index.js
import React from 'react'
import Default from '../layouts/default'
import axios from 'axios'
const meta = { title: 'Index title', description: 'Index description' }

class IndexPage extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      loading: true,
      dog: {}
    }
    this.fetchData = this.fetchData.bind(this)
  }
  async componentDidMount () {
    await this.fetchData()
  }
  async fetchData () {
    this.setState({ loading: true })
    const { data } = await axios.get(
      'https://api.thedogapi.com/v1/images/search?limit=1'
    )
    this.setState({
      dog: data[0],
      loading: false
    })
  }
  render () {
    return (
      <Default meta={meta}>
        <div>
          <h1>This is the Front Page.</h1>
          <h3>Random dog of the day:</h3>
          <img src={this.state.dog.url} alt='' />
        </div>
      </Default>
    )
  }
}

export default IndexPage
Enter fullscreen mode Exit fullscreen mode

The index.js file will be rendered on the root path of our app. It calls a dog API and will show a picture of a cute dog.

Let's create more routes. Create a sub-folder called dogs and create an index.js file and a _breed.js file in there. The index.js will be rendered at the /dogs route while the _breed.js will be rendered at /dogs/:breed where the :breed represents a route parameter.

Add this to the index.js in the dogs directory.

// pages/dogs/index.js
import React from 'react'
import axios from 'axios'
import Default from '../../layouts/default'
const meta = { title: 'Dogs title', description: 'Dogs description' }

class DogsPage extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      loading: true,
      dogs: []
    }
    this.fetchData = this.fetchData.bind(this)
  }
  async componentDidMount () {
    await this.fetchData()
  }
  async fetchData () {
    this.setState({ loading: true })
    const { data } = await axios.get(
      'https://api.thedogapi.com/v1/images/search?size=thumb&limit=10'
    )
    this.setState({
      dogs: data,
      loading: false
    })
  }
  renderDogList () {
    return (
      <ul>
        {this.state.dogs.map((dog, key) =>
          <li key={key}>
            <img src={dog.url} alt='' />
          </li>
        )}
      </ul>
    )
  }
  render () {
    return (
      <Default meta={meta}>
        <div>
          <h1>Here you have all dogs.</h1>
          {this.renderDogList()}
        </div>
      </Default>
    )
  }
}

export default DogsPage
Enter fullscreen mode Exit fullscreen mode

And, another snippet in the _breed.js file in the dogs folder.

// pages/dogs/_breed.js
import React from 'react'
import axios from 'axios'
import Default from '../../layouts/default'

class DogBreedPage extends React.Component {
  static getInitialProps ({ query: { breed } }) {
    return { breed }
  }
  constructor (props) {
    super(props)
    this.state = {
      loading: true,
      meta: {},
      dogs: []
    }
    this.fetchData = this.fetchData.bind(this)
  }
  async componentDidMount () {
    await this.fetchData()
  }
  async fetchData () {
    this.setState({ loading: true })
    const reg = new RegExp(this.props.breed, 'g')

    const { data } = await axios.get(
      'https://api.thedogapi.com/v1/images/search?size=thumb&has_breeds=true&limit=50'
    )
    const filteredDogs = data.filter(dog =>
      dog.breeds[0]
        .name
        .toLowerCase()
        .match(reg)
    )
    this.setState({
      dogs: filteredDogs,
      breed: this.props.breed,
      meta: { title: `Only ${this.props.breed} here!`, description: 'Cute doggies. :D' },
      loading: false
    })
  }
  renderDogList () {
    return (
      <ul>
        {this.state.dogs.map((dog, key) =>
          <li key={key}>
            <img src={dog.url} alt='' />
          </li>
        )}
      </ul>
    )
  }
  render () {
    return (
      <Default meta={this.state.meta}>
        <div>
          <h1>Dog breed: {this.props.breed}</h1>
          {this.renderDogList()}
        </div>
      </Default>
    )
  }
}

export default DogBreedPage
Enter fullscreen mode Exit fullscreen mode

As you can see in the Default component we're injecting custom meta tags. It will add custom fields in the <head> of your page, giving it proper SEO support!

Note: If you're stuck, here's what the code looks like in the repo.

Let's deploy it and see if it works.

Deploy the app to AWS Lambda

At the very beginning, we added a script to our package.json called deploy. It'll build the Next app and deploy the serverless service as we specified in the serverless.yml.

All you need to do is run:

$ npm run deploy
Enter fullscreen mode Exit fullscreen mode

The terminal will return output with the endpoint for your app. We also need to add the domain for it to work properly. We've already added the configuration in the serverless.yml but there's one more command we need to run.

$ sls create_domain
Enter fullscreen mode Exit fullscreen mode

This will create a CloudFront distribution and hook it up to your domain. Make sure that you've added the certificates to your AWS account. It usually takes around 20 minutes for AWS to provision a new distribution. Rest your eyes for a moment.

Once you're back, go ahead and deploy it all again.

$ npm run deploy
Enter fullscreen mode Exit fullscreen mode

It should now be tied up to your domain. Here's what it should look like.

Nice! The app is up-and-running. Go ahead and try it out.

How to gain insight into your system?

The #1 problem with all serverless applications are their distributed nature. Plain and simple, it's impossibly hard to have an overview of all the things going on. Not to mention how hard it is to debug when something goes wrong.

To calm my fears I use Tracetest. It gives you end-to-end testing & debugging powered by OpenTelemetry.

Thankfully, there's detailed documentation, which makes the onboarding process a breeze. Go ahead and follow the Quick Start guide. Don't forget to come back here though. 😄

Wrapping up

This walkthrough was a rollercoaster of emotions! It gives you a new perspective into creating fast and performant single-page apps while at the same time keeping the SEO capabilities of server-rendered apps. However, with a catch. There are no servers you need to worry about. It's all running in a serverless environment on AWS Lambda. It's easy to deploy and scales automatically. Doesn't get any better.

If you got stuck anywhere take a look at the GitHub repo for further reference, and feel free to give it a star if you want more people to see it on GitHub.

GitHub logo adnanrahic / serverless-side-rendering-react-next

Sample repo for setting up Next and React on AWS Lambda with the Serverless Framework.

Serverless-Side Rendering React Next

Sample repo for setting up Next and React on AWS Lambda with the Serverless Framework.






If you want to read some of my previous serverless musings head over to my profile or join my newsletter!

Or, take a look at a few of my articles right away:

I also highly recommend checking out this article about Next.js, and this tutorial about the serverless domain manager.

Hope you guys and girls enjoyed reading this as much as I enjoyed writing it. If you liked it, slap that tiny heart so more people here on dev.to will see this tutorial. Until next time, be curious and have fun.


Disclaimer: Tracetest is sponsoring this blogpost. Use observability to reduce time and effort in test creation + troubleshooting by 80%.


Top comments (28)

Collapse
 
dallashuggins profile image
Dallas Huggins • Edited

Great tutorial - thank you! I finished the tutorial and successfully deployed, but am getting errors when navigating to the site. When navigating to https://[unique id].execute-api.us-east-1.amazonaws.com/dev/, I get the message {"message": "Internal server error"}. One nuance is that I removed the customDomain section, as I was hoping to use the AWS endpoint.

One thing I did when testing, is I added "dev": "next" to the scripts object in the package.json, so when I run npm run dev, I get the correct information displaying at localhost:3000, localhost:3000/dogs, etc.

Also, I added the code below to the server.js file, and when I navigate to https://[unique id].execute-api.us-east-1.amazonaws.com/dev/test, I get the 'Hello World!' message displaying, as expected:

server.get('/test', (req, res) => {
res.send('Hello World!');
});

The full server.js file is below. The server.get function with the res.send works, but the server.get functions with the app.render always throw an error.

const express = require('express');
const path = require('path');
const dev = process.env.NODE_ENV !== 'production';
const next = require('next');
const pathMatch = require('path-match');
const app = next({ dev });
const handle = app.getRequestHandler();
const { parse } = require('url');

const server = express();
const route = pathMatch();
server.use('/next', express.static(path.join(_dirname, '.next')));
server.get('/test', (req, res) => {
res.send('Hello World!');
});
server.get('/', (req, res) => {
app.render(req, res, '/')
});
server.get('/dogs', (req, res) => {
app.render(req, res, '/dogs')
});
server.get('/dogs/:breed', (req, res) => {
const params = route('/dogs/:breed')(parse(req.url).pathname)
return app.render(req, res, '/dogs/_breed', params)
});
server.get('*', (req, res) => handle(req, res))

module.exports = server;

Any insight would be greatly appreciated! Let me know if I can provide any additional information. Sorry if I'm doing anything real obvious, as I am completely new to Next.js and still fairly beginner with AWS.

Collapse
 
drumline18 profile image
drumline18 • Edited

I would add the same exact comment. I get Internal server error. If I try to add the /test get route, it works fine. If I try next locally it displays fine on localhost:3000. I myself has not even removed the customDomain section as I have a domain on Route 53 for that.

I have just tried copying the repo provided in the article and deploying it to my domain. Still the same Internal server error.

Collapse
 
hzburki profile image
Haseeb Burki

I get the same error when deploying the app to AWS.

Error:
{
message: "Internal server error"
}

The routes work fine locally, except the dynamic _breed route which results in a 404.

I've tried everything myself and also cloned the git repo as is to test. Both give the exact same result mentioned above. I have a custom domain linked via "sls create_domain" command.

Collapse
 
shood profile image
s-hood • Edited

for local development and testing:
npm run dev will run it has a next app without serverless (only pages are available, no serverless functionality written in server.js). You need to use serverless-offline github.com/dherault/serverless-off...

Collapse
 
its_ku profile image
Mo City Donald

Same here. I'm getting the internal server error and the issue has to do with calling app.render. Further research leads me to believe that the problem stems from not calling app.prepare before configuring the server.

Collapse
 
qm3ster profile image
Mihail Malo

Hi, this raised a couple a questions with me:

  1. What benefit does serverless provide in this scenario? What are the alternatives(aws cli?terraform?idk.)?
  2. Why is your article on nuxt mentioned only in the list at the end, although this is almost exactly the same thing again?
Collapse
 
adnanrahic profile image
Adnan Rahić

Hey, glad to answer.

  1. The Serverless Framework makes it easy to deploy resources and create a domain. You can use whatever tool you like, AWS SAM, the AWS CLI, CloudFormation, Terraform, etc. It's totally up to you what you want to use. Using the Serverless Framework lowers the barrier to entry for devs who are not familiar to the services and tools AWS provides. It's just easier to get started with.
  2. It's mentioned at the end solely because Vue and React are two totally different frameworks with different ecosystems. I didn't want to create confusion. Vue devs can follow the Nuxt guide, while React devs can follow this one. :)

Hope this answers your questions. Feel free to let me know if you have any more. :D

Collapse
 
sebas profile image
Sebastian Mantilla

Hi,

Once I deploy this, I'm able to load the front page, but all the next resources (*.js) come back as 403, any idea why?

Thanks for your help,

Collapse
 
clarsen profile image
Case Larsen • Edited

FWIW, you're probably seeing issue from not deploying with custom domain and needing to have the stage in the path to access the resource, e.g.
https://XXXXX.execute-api.us-east-1.amazonaws.com/production/_next/static/YYYYY/pages/index.js is needed but https://XXXXX.execute-api.us-east-1.amazonaws.com/_next/static/YYYYY/pages/index.js is what the express response actually renders.

This explains some of issue and attempts to make it work github.com/dougmoscrop/serverless-...

as well as this: github.com/zeit/next.js/issues/6447

Collapse
 
adnanrahic profile image
Adnan Rahić

Can you try cloning the repo and deploying it? If that works, there has to be a strange issue somewhere in your code. Compare your project to the cloned repo. Hopefully, that'll help you out. :)

Collapse
 
hzburki profile image
Haseeb Burki

It doesn't work when I clone it from the repo either. The only thing I've changed were the values in secret.json

On local routes work fine, except for the dynamic "_breed" route. It results in 404 Not Found.

On deploying to AWS all routes result in message: "Internal server error" ... you can check it out at ecom.ideamappers.com

Collapse
 
drumline18 profile image
drumline18

That does not work. It gives and internal server error as soon as you try using app.render.

Collapse
 
joachimgoo profile image
joachimgoo

Hi,

I have exactly the same issue.
Where you able to solve it?

Thanks

Collapse
 
garyburgmann profile image
Gary Burgmann

I have stumbled on a few of your tutorials Adnan and they are all awesome. I actually read through all of this before realising it was you (I got into Serverless when I read your Express, Mongo + Serverless tutorial on hackernoon). NextJS is great and I am stoked that it is just as easy to deploy to Lambda. Thank you!

Collapse
 
adnanrahic profile image
Adnan Rahić

Hey Gary! Wow, thanks for the kind words. I'm glad I can help the community understand serverless architectures. Stay tuned for more tutorials. :)

Collapse
 
arswaw profile image
Arswaw

This was the error I received after I ran 'npm run deploy' for the first time.

I have a domain name that ends in .info

Could that be the cause?


Error: Could not set up basepath mapping. Try running sls create_domain first.

Error: Error: 'react-ssr.happyweather.info' could not be found in API Gateway.

NotFoundException: Invalid domain name identifier specified
Collapse
 
felixseip profile image
Felix Seip • Edited

Great tutorial - thanks alot! I am new to the NextJS x AWS Lambda stack. When I call the function url, all I get is a random string like: PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CjxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KPHRpdGxlPkVycm9yPC90aXRsZT4KPC9oZWFkPgo8Ym9keT4KPHByZT5DYW5ub3QgR0VUIC88L3ByZT4KPC9ib2R5Pgo8L2h0bWw+Cg==

Locally everything works fine and I can see that NextJS is rendering my pages. However, on lambda all I get is that random string.
Do you know why this could be happening? Thanks alot :D

Collapse
 
joshglazer profile image
Josh Glazer

Is it possible to display images from the static folder once the site is deployed? I added an image for a logo to the static folder and am displaying it in the header component. It shows while I'm developing on localhost, but when I deploy to AWS it shows as a broken image.

Here's my code:
github.com/joshglazer/freetimeupda...

Collapse
 
joshglazer profile image
Josh Glazer

I figured this out myself. In case anyone's curious, you can fix this by adding the following line of code to your server.js file.

server.use('/static', express.static('static'))

Collapse
 
ivanoats profile image
Ivan Storck

Have you done any performance testing on this setup? I would be interested to know some numbers like TTFB and others.

Collapse
 
adnanrahic profile image
Adnan Rahić

I haven't actually. I'd be stoked to know the performance too. Once I find the time, I'll surely check it out. If you get around to doing it yourself, please let me know! :D

Collapse
 
arswaw profile image
Arswaw

I saw that Next.js 8 was released after you published this article. It has a section about how serverless is now supported. Should I do anything differently because of this?

Collapse
 
jsteixeira profile image
Joao

Hey, this is great thanks! For those following the tutorial and copying and pasting the code, there is a typo in index.js.. it is missing ). FYI

Collapse
 
adnanrahic profile image
Adnan Rahić

Oh wow, nice catch! Thanks for that. I've gone in and fixed the typo. I'm glad you like the tutorial. :)

Collapse
 
grath90 profile image
Cole

Considering lambda cold starts; what does this do for initial load times?

Collapse
 
bitttttten profile image
bitten

So you keep the secrets file locally? What if you are on a team and want to deploy this through CI/CD? Would you handle that on there?

Collapse
 
adnanrahic profile image
Adnan Rahić

You use AWS KMS. Here's a nice Serverless plugin. :)

nordcloud / serverless-kms-secrets

🔑🔐☁️ Serverless plugin to encrypt variables with KMS

Serverless KMS Secrets

A Serverless Plugin for the Serverless Framework which helps with encrypting service secrets using the AWS Key Management Service (KMS)

Introduction

This plugins does the following:

  • It provides commands to encrypt and decrypt secrets with KMS

Installation and configuration

In your service root, run:

npm install --save-dev serverless-kms-secrets

Add the plugin to serverless.yml:

plugins
  - serverless-kms-secrets

Configure the plugin into the custom block in serverless.yml. For example:

custom
  serverless-kms-secrets
    secretsFile: kms-secrets.${opt:stage, self:provider.stage}.${opt:region, self:provider.region}.yml (optional)
  kmsSecrets: ${file(kms-secrets.${opt:stage, self:provider.stage}.${opt:region, self:provider.region}.yml)}

By default, the plugin creates secrets to the file kms-secrets.[stage].[region].yml. This can be overriden with the secretsFile parameter in the serverless-kms-secrets configuration.

Add Decrypt permissions to your lambda function with e.g. this block in IamRoleStatements:

    - Effect: Allow
      Action:
      - KMS:Decrypt
      Resource: ${self:custom.kmsSecrets.keyArn} 

Usage

Creating KMS Key

Create a KMS key in AWS IAM service, under Encryption keys. Collect…