Front-End Web & Mobile

SSR Support for AWS Amplify JavaScript Libraries

AWS Amplify is a set of tools and services that enable mobile and front-end web developers to build secure, scalable full stack applications powered by AWS. It consists of three main components: a set of open source libraries and UI components for adding cloud-powered functionalities, a CLI toolchain to create and manage cloud backends, and the Amplify Console, an AWS Service to deploy and host static sites and full stack serverless web applications.

Modern web frameworks like Next.js and Nuxt.js combine hybrid static site generation (SSG) and server-side rendering (SSR) along with traditional client rendering to enable developers to build fast, modern sites while also providing a great developer experience. These technologies continue to grow in popularity as developers and customers increasingly take advantage of their features like API routes, incremental static regeneration, code-splitting, and good SEO.

With the latest release, the Amplify JavaScript libraries add additional features that further enable developers to build SSR apps using modern web frameworks like Next.js and Nuxt.js with major enhancements to the REST API, GraphQL API, Auth, and DataStore categories.

Amplify will now seamlessly persist user sessions from the client to the server, enabling developers to make authentication calls and access data. Using Amplify JavaScript, developers can fetch data with the GraphQL API, REST API, and DataStore on the server to pre-render their page at build-time (when generating static sites like Gatsby) or per-request (using frameworks like Next.js & Nuxt.js). These capabilities make Amplify an ideal data layer for JAMstack, serverless, and server-rendered apps.

In this post, you’ll learn how to use these enhancements with Next.js in the following ways:

  1. Enabling Server-Side Rendering (SSR) support in an Amplify app
  2. Using the API class to hydrate a statically generated page with data from a GraphQL API in getStaticProps
  3. Using the Auth class to check and determine user authentication status in an API route before returning data in the API response
  4. Using the API class in a server-rendered page to make an authenticated API request to a GraphQL back end and pass props into a page using getServerSideProps

You’ll also learn how to deploy your Next.js app to AWS in the following ways:

  1. Deploying a static Next.js app using AWS Amplify
  2. Deploying a Next.js app with SSR and API routes using the Serverless Framework

You can also learn how to build SSR Next.js apps using Amplify using the new Next.js getting started guide here.

Enabling Server-Side Rendering (SSR) support in an Amplify app

When using the Amplify CLI, the aws-exports.js file gets created and updated automatically for you based upon the resources you have added and configured.

For client-only or statically generated apps, Amplify.configure(awsExports) is all you need.

To enable SSR support, provide ssr: true in your Amplify configuration:

// pages/_app.js
import { Amplify } from "aws-amplify";
import awsExports from "../src/aws-exports";
Amplify.configure({ ...awsExports, ssr: true });

If you are using getStaticProps with fallback: true then you will also need to configure Amplify SSR mode in that file.

Hydrating a component in getStaticProps

The getStaticProps method of Next.js enables you to pre-render a static page with data passed in as props. Using this method you can do things like make API calls to fetch dynamic data that the page depends on at build-time.

For example, consider a page that renders a dynamic list of items returned from an API call. With the recent updates to the Amplify libraries, you can make these requests using both API Key and IAM authorization without having to change anything in your codebase:

import { API } from 'aws-amplify';
import { listMovies } from '../src/graphql/queries';

export default function Movies(props) {
  const { movies } = props;
  return (
    <div>
       {
          movies.map(movie => (
            <div key={movie.id}>
              <h3>{movie.name}</h3>
              <span>{movie.description}</span>
            </div>
          ) )
       }
    </div>
  )
}

export async function getStaticProps() {
  const movieData = await API.graphql({ query: listMovies });
  return {
    props: {
      movies: movieData.data.listMovies.items
    }
  }
}

Checking user authentication in an API route

API routes enable you to easily create API endpoints using Next.js. Any file inside the folder pages/api/** is treated as an API endpoint instead of a page. API routes export a function that receives a request (req) and response (res) parameter and return some type of response (typically JSON).

When working within these functions, you often need to gain access to the user’s identity in order to make secure API requests that either depend on the state of the signed in user or that perform some type of authorization or fine grained access control based on the signed in user.

Using the new withSSRContext API you can gain access to the currently authenticated user session by using Amplify API class along with the request object of the function:

// pages/api/profile.js
import Amplify, { withSSRContext } from "aws-amplify";
import config from "../../aws-exports.js";

// Amplify SSR configuration needs to be done within each API route
Amplify.configure({ ...config, ssr: true });
export default async (req, res) => {
  const { Auth } = withSSRContext({ req });

  let data;
  let user;
  try {
    user = await Auth.currentAuthenticatedUser();
    console.log('user is authenticated');
    // fetch some data and assign it to the data variable
  } catch (err) {
    console.log('error: no authenticated user');
  }
  
  res.statusCode = 200;
  res.json({
    data: data ? data : null,
    username: user ? user.username : null
  })
}

Making an authenticated API request in getServerSideProps

The getServerSideProps function enables you to opt-in to server-side rendering of a component in Next.js. The framework will pre-render these pages on each request passing in any data that is returned as props.

Using the new withSSRContext utility you can make authenticated API calls to GraphQL and REST back ends from these server-rendered routes.

For example, take an AppSync GraphQL API that is backed by an identity provider such as Amazon Cognito User pools, Okto, or Auth0. Some GraphQL types may require a user to be authenticated to perform certain requests. Using the API class, the user identity will now automatically be configured and passed into the API request headers:

import { withSSRContext } from 'aws-amplify';
import { listMovies } from '../src/graphql/queries';

export default function Movies(props) {
  const { movies } = props
  return (
    <div>
       {
          movies && movies.map(movie => (
            <div key={movie.id}>
              <h3>{movie.name}</h3>
              <span>{movie.description}</span>
            </div>
          ) )
       }
    </div>
  )
}

export async function getServerSideProps(context) {
  const { API } = withSSRContext(context)
  let movieData
  try {
    movieData = await API.graphql({
      query: listMovies,
      authMode: "AMAZON_COGNITO_USER_POOLS"
    });
    console.log('movieData: ', movieData)
  } catch (err) {
    console.log("error fetching movies: ", err)
  }
  return {
    props: {
      movies: movieData ? movieData.data.listMovies.items : null
    }
  }
}

Deploying a static Next.js app using Amplify

If you are using Next.js with and do not need to take advantage of API routes or server-side rendering (getServerSideProps), you can use Amplify to host your static Next.js site.

Amplify leverages Cloudfront to deploy your app and have it automatically distributed globally to hundreds of points of presence around the world.

You can deploy your site via the Amplify CLI or in just a few clicks via the Amplify Console from any Git repo. In this section, you’ll learn how to deploy via the Amplify CLI.

Installing the Amplify CLI

If you do not already have the Amplify CLI installed and configured, install it now:

npm install -g @aws-amplify/cli

amplify configure

For a full walkthrough of how to install and configure the CLI, see this video

Now, in your Next.js app, update the build script in package.json to add the export command:

"build": "next build && next export",

Next, in the root of the Next.js app, initialize a new Amplify project:

amplify init
? Enter a name for the project: mynextapp
? Enter a name for the environment: dev
? Choose your default editor: Visual Studio Code (or your preferred editor)
? Choose the type of app that you're building: javascript
? What javascript framework are you using: react
? Source Directory Path: src
? Distribution Directory Path: out
? Build Command: npm run-script build
? Start Command: npm run-script start
? Do you want to use an AWS profile? Y
? Please choose the profile you want to use: <your profile>

Setting the Distribution Directory Path to out is important for deploying a static Next.js site to Amplify as this is the directory that Next.js will create the static assets from the build phase.

Now, add hosing with the Amplify add command:

amplify add hosting

? Select the plugin module to execute: Hosting with Amplify Console
? Choose a type: Manual deployment

Next, deploy the app:

amplify publish

⚡️ Congratulations, your app has now been successfully deployed! The URL for the app should be displayed in your terminal.

To see your app in the Amplify console at any time, run the following command:

amplify console

Deploying an SSR Next.js app using AWS Amplify

If you would like to deploy a Next.js app and take advantage of things like API routes and server-side rendering, the easiest way to get up and running is using the Amplify Console. Checkout this blog post to learn how to deploy a hybrid Next app with feature branch deployments, custom domains, PR previews and more.

Conclusion

To learn more about Next.js support with AWS Amplify, check out the getting started guide as well as the documentation here.