No more waiting! It’s finally here, AWS Amplify hosting for Next.js server-side rendering (SSR).
Why this is great: It’s no joke, I wait for this feature for more than a year. And is finally here and is fast!
Not so great: Next 10.x.x was a huge leap forward. All things I wanted to try, right off the bat, and backed in my applications...But this will be on hold, as the time of writing, this launch only his support is for Next.js 9.x.x version. Considering version 10.x is from October 2020 I think the pace is a little slow to catchup.
To evaluate what type of rendering your application need, I recommend the post Next.js: Server-side Rendering vs. Static Generation by Lee Robinson—and in the real world sometimes you’ll need both.
The webapp
For all purposes, you could do with your app or create a boilerplate new NextJS with SSR. I created this barebones site that renders some info from the PokéAPI. This is the repository on GitHub:
ibrahimcesar / nextjs-ssr-amplify-aws
👾 Repository to test NextJS Server Side Rendering with AWS Amplify
👾 AWS Amplify NextJS SSR
Example of NextJS SSR example to deploy with AWS Amplify SSR Hosting using the PokéAPI
Developed in 🇧🇷 Brazil
Deploying
If you never used Amplify
You’ll need to have the amplify installed and configured. Just follow the docs and you’ll be ready to go. Or make sure you have the latest version.
Amplify Init
❯ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project pokessr
The following configuration will be applied:
Project information
| Name: pokessr
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm run-script build
| Start Command: npm run-script start
? Initialize the project with the above configuration? Yes
Using
…You can leave all build options as it is, because Amplify will automatically pick up as SSR and deploy it. For this to happen you also don’t need (or have to) choose a different export folder. If you clone my repo, you could check that I’m indeed using NextJS 10.0.0
but, I cannot use any additional feature like the new Image
component.
Deploying
If you never used Amplify
You’ll need to have the amplify installed and configured. Just follow the docs and you’ll be ready to go. Or make sure you have the latest version.
Amplify Init
❯ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project pokessr
The following configuration will be applied:
Project information
| Name: pokessr
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm run-script build
| Start Command: npm run-script start
? Initialize the project with the above configuration? Yes
Using default provider awscloudformation
? Select the authentication method you want to use: AWS profile
For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
? Please choose the profile you want to use amplify
Besides the name, pokessr
, I only choose my profile to deploy, called amplify
but you can deploy in whatever profile you configured. I accepted all defaults. Then amplify will create your environment:
Adding backend environment dev to AWS Amplify Console app: d31r520fbr96mj
⠙ Initializing project in the cloud...
CREATE_IN_PROGRESS amplify-pokessr-dev-185133 AWS::CloudFormation::Stack Tue May 18 2021 18:51:41 GMT-0300 (Horário Padrão de Brasília) User Initiated
CREATE_IN_PROGRESS UnauthRole AWS::IAM::Role Tue May 18 2021 18:51:45 GMT-0300 (Horário Padrão de Brasília)
CREATE_IN_PROGRESS AuthRole AWS::IAM::Role Tue May 18 2021 18:51:45 GMT-0300 (Horário Padrão de Brasília)
CREATE_IN_PROGRESS DeploymentBucket AWS::S3::Bucket Tue May 18 2021 18:51:46 GMT-0300 (Horário Padrão de Brasília)
CREATE_IN_PROGRESS UnauthRole AWS::IAM::Role Tue May 18 2021 18:51:46 GMT-0300 (Horário Padrão de Brasília) Resource creation Initiated
CREATE_IN_PROGRESS AuthRole AWS::IAM::Role Tue May 18 2021 18:51:46 GMT-0300 (Horário Padrão de Brasília) Resource creation Initiated
⠇ Initializing project in the cloud...
CREATE_IN_PROGRESS DeploymentBucket AWS::S3::Bucket Tue May 18 2021 18:51:46 GMT-0300 (Horário Padrão de Brasília) Resource creation Initiated
⠸ Initializing project in the cloud...
CREATE_COMPLETE AuthRole AWS::IAM::Role Tue May 18 2021 18:51:59 GMT-0300 (Horário Padrão de Brasília)
CREATE_COMPLETE UnauthRole AWS::IAM::Role Tue May 18 2021 18:51:59 GMT-0300 (Horário Padrão de Brasília)
⠹ Initializing project in the cloud...
CREATE_COMPLETE DeploymentBucket AWS::S3::Bucket Tue May 18 2021 18:52:08 GMT-0300 (Horário Padrão de Brasília)
CREATE_COMPLETE amplify-pokessr-dev-185133 AWS::CloudFormation::Stack Tue May 18 2021 18:52:10 GMT-0300 (Horário Padrão de Brasília)
✔ Successfully created initial AWS cloud resources for deployments.
✔ Initialized provider successfully.
Initialized your environment successfully.
Your project has been successfully initialized and connected to the cloud!
Some next steps:
“amplify status” will show you what you’ve added already and if it’s locally configured or deployed
“amplify add <category>“ will allow you to add features like user login or a backend API
“amplify push” will build all your local backend resources and provision it in the cloud
“amplify console” to open the Amplify Console and view your project status
“amplify publish” will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud
Pro tip:
Try “amplify add api” to create a backend API and then “amplify publish” to deploy everything
What we are going to use is hosting
.
And by zero configuration, you just need to connect your repository and the building settings will be set.
And you can always have a look at how the build is going accessing the logs in the AWS Amplify console. For our purposes, see a Starting SSR Build
in your logs:
2021-05-18T22:35:49.379Z [INFO]: info - Creating an optimized production build...
2021-05-18T22:35:58.592Z [INFO]: info - Compiled successfully
info - Collecting page data...
2021-05-18T22:35:59.098Z [INFO]: info - Generating static pages (0/28)
2021-05-18T22:35:59.480Z [INFO]: info - Generating static pages (7/28)
2021-05-18T22:35:59.600Z [INFO]: info - Generating static pages (14/28)
2021-05-18T22:35:59.706Z [INFO]: info - Generating static pages (21/28)
2021-05-18T22:35:59.797Z [INFO]: info - Generating static pages (28/28)
2021-05-18T22:35:59.797Z [INFO]: info - Finalizing page optimization...
2021-05-18T22:35:59.814Z [INFO]:
2021-05-18T22:35:59.860Z [INFO]: Page Size First Load JS
┌ λ / 1.32 kB 68.7 kB
├ /_app 0 B 64.2 kB
├ λ /[ditto] 1.39 kB 68.7 kB
├ ○ /404 2.76 kB 66.9 kB
├ ● /pokemons/[name] 1.53 kB 68.9 kB
├ ├ /pokemons/bulbasaur
├ ├ /pokemons/ivysaur
├ ├ /pokemons/venusaur
├ └ [+22 more paths]
└ λ /random 1.39 kB 68.7 kB
+ First Load JS shared by all 64.2 kB
├ chunks/commons.b2f5db.js 13.5 kB
├ chunks/framework.149f13.js 42 kB
├ chunks/main.e0d560.js 6.8 kB
├ chunks/pages/_app.9245f7.js 865 B
├ chunks/webpack.f82c36.js 950 B
└ css/b8e1ed54af27c57535f7.css 897 B
2021-05-18T22:35:59.861Z [INFO]: λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
○ (Static) automatically rendered as static HTML (uses no initial props)
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
(ISR) incremental static regeneration (uses revalidate in getStaticProps)
2021-05-18T22:35:59.993Z [INFO]: Starting SSR Build...
2021-05-18T22:37:10.138Z [INFO]: SSR Build Complete.
2021-05-18T22:37:11.159Z [INFO]: # Completed phase: build
2021-05-18T22:37:11.159Z [INFO]: ## Build completed successfully
Then you gave to wait a couple of minutes and your application you be on your custom domain or in the generate domain of Amplify. For this demonstration my web apps is available here.
Pages
The front page is itself server side generated:
export const getServerSideProps: GetServerSideProps = async () => {
const data = await getPokemons()
return {
props: {
data
}
}
}
It will query in the PokéAPI and return all Pokémons until reaching the number or the maximum today. I placed 3000
but as you can check, the actual number today is 1118
.
When you click in a Pokémon, I use a dynamic route in the file ditto
to generate the Pokémon by the name. Pokémon fans will get the reference. And [ditto].tsx
is also SSR.
export const getServerSideProps: GetServerSideProps = async (context) => {
let data;
const { ditto} = context.query
if (typeof ditto === ‘string’) {
data = await getPokemonData(ditto)
} else {
data = {}
}
return { props: { data } }
}
But for fun I created a random
page... that renders a random Pokémons to test even better the SSR. It get all the possible Pokémons and returns one at random using Math.random()
:
export const getServerSideProps: GetServerSideProps = async () => {
const random = await getPokemons() as Pokedex
const ditto = random.results[random.results.length * Math.random() | 0].name
let data;
if (typeof ditto === ‘string’) {
data = await getPokemonData(ditto)
} else {
data = {}
}
return {
props: {
data
}
}
}
And to test ISG (Incremental Static Generation) I created a folder called pokemons
. Stable static generation was added to Next 9.3 but my test doesn’t show that works right now with the Amplify SSR hosting, it defaults to the SSR. ISG is a mechanism to update existing pages, by re-rendering them in the background as traffic comes in using the property revalidate
. Also, another great use is, per example, you have a specific dataset of pages to generate at build time but you’ll need on dynamic routes to be generated new pages as soon you publish another in your headless CMS or database. ISG generated even a bounty as feature in another project and unlocks a lot of interesting use cases.
export const getStaticProps: GetStaticProps = async (context) => {
let data
if (context.params) {
data = await getPokemonData(context.params.name as string)
} else {
data = {}
}
return {
props: {
data,
date: new Date().toISOString(),
},
revalidate: 60 * 5
}
};
export const getStaticPaths: GetStaticPaths<{ name: string }> = async () => {
const pokemons = await getPokemons(25) as Pokedex
const paths = pokemons.results.map((pokemon) => {
return { params: { name: pokemon.name.toString() } }
})
return {
fallback: true,
paths,
}
};
For my example, I generate at build time the first 25 Pokémons. See that my props are changing, I’m passing a dynamic date. But my first 25 Pokémons, starting with Bulbasaur and going up to Pikachu. They have a text Generated at
that will not revalidate at all (right now configured to re-validate at each 5 minutes revalidate: 60 * 5
in which it should change this date. But if you access any other Pokémon than the first 25, like the number 26, Raichu or the 186, Scizor will be server-side generated at the time and you’ll see the date of any time you accessing again, so no ISG. This feature was not advertised but since it was stable at 9.3+ and I did not found work and it happens you are after this, you’ll need to wait or surprise me show-me what I’m getting wrong here because I would love to have ISG already 😀
I also created a private first component for YouTube embeds if you plan to use something like this in your Next app, check it out:
Why I made my open source React component private by default: an Open Source story
Ibrahim Cesar ・ May 18 '21
Please leave your thoughts, takes and insights in the comments! Or problems if you have one or the solution to ISG!
Top comments (0)