Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated Apr 29, 2024

Server-Side Rendering

SSR functionality in Amplify was primarily built for compatibility with Next.js page components, and their getServerSideProps data fetching mechanism. Compatibility with other frameworks or Next.js features cannot be guaranteed.

To work well with server-rendered pages, Amplify JS requires slight modifications from how you would use it in a client-only environment.

Amplify

Enabling SSR

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 apps, Amplify.configure(awsExports) is all you need.

To enable SSR support, also provide ssr: true:

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

By providing ssr: true, Amplify persists credentials on the client in cookies so that subsequent requests to the server have access to them.

Note: Once vercel/next.js#16977 is resolved, you can hoist Amplify.configure into pages/_app.js. Until then, be sure that all pages/* run Amplify.configure({ ...awsExports, ssr: true }).

withSSRContext

Once an application has been configured with ssr: true, client-side credentials are passed to the server via cookies.

The withSSRContext utility creates an instance of Amplify scoped to a single request (req) using those cookie credentials.

For client-side code rendered in the browser, your page should continue using top-level imports as usual:

import { Amplify, API } from "aws-amplify";
import awsExports from "../src/aws-exports";
Amplify.configure({ ...awsExports, ssr: true });
export default function HomePage({ posts = [] }) {
const [posts, setPosts] = useState(posts);
useEffect(() => {
// Notice how the client correctly uses the top-level `API` import
API.graphql({ query: listPosts }).then(({ data }) => setPosts(data.listPosts.items));
}, [])
return ( ... );
}

When on the server, use withSSRContext({ req?: ServerRequest }):

import { Amplify, API, withSSRContext } from 'aws-amplify';
export async function getServerSideProps({ req }) {
// Notice how the server uses `API` from `withSSRContext`, instead of the top-level `API`.
const SSR = withSSRContext({ req });
const { data } = await SSR.API.graphql({ query: listPosts });
return {
props: {
posts: data.listPosts.items
}
};
}

Server-side functions that don't have a req object (e.g. Next.js' getStaticProps & getStaticPaths) should still use withSSRContext(). Please note that it's not possible to perform authenticated requests with Amplify when using these functions.

Use Amplify with Next.js App Router ("app/" directory)

Amplify JavaScript can be used with the Next.js App Router (Next.js v13.4+) by applying the following changes:

1. Run Amplify.configure({ ...awsExports, ssr: true }) in both the client-side and server-side code

To use Amplify with Next.js App Router, you must run Amplify.configure() in both Client and Server Components. The ssr option must be enabled.

Example Next.js App Router Client Component containing your login flow (e.g. app/page.tsx):

'use client';
import { Amplify } from 'aws-amplify';
import awsExports from '@/aws-exports';
Amplify.configure({ ...awsExports, ssr: true });
export default function Home() {
return {
/* Render a login form for your users */
};
}

Example Next.js App Router Server Component (e.g. app/posts/page.tsx):

import { Amplify } from 'aws-amplify';
import awsExports from '@/aws-exports';
Amplify.configure({ ...awsExports, ssr: true });
export default async function Page() {
// ...
}

2. Prepare a request object for withSSRContext to perform server-side operations that require authentication

Example Next.js App Router Server Component that queries data from GraphQL (e.g. app/posts/page.tsx):

import { Amplify, withSSRContext } from 'aws-amplify';
import { headers } from 'next/headers';
import { listPosts } from '@/graphql/queries';
import awsExports from '@/aws-exports';
Amplify.configure({ ...awsExports, ssr: true });
export default async function Page() {
// Construct a req object & prepare an SSR enabled version of Amplify
const req = {
headers: {
cookie: headers().get('cookie')
}
};
const SSR = withSSRContext({ req });
const { data } = await SSR.API.graphql({ query: listPosts });
// ...
}

DataStore

Serializing

For Next.js, returned props from the server have to be valid JSON. Because DataStore.query(Model) returns instances of Model, you need the serializeModel helper to convert it to JSON instead:

import { serializeModel } from '@aws-amplify/datastore/ssr';
import { Amplify, withSSRContext } from "aws-amplify";
...
export async function getServerSideProps({ req }) {
const SSR = withSSRContext({ req });
const posts = await SSR.DataStore.query(Post);
return {
props: {
// This converts Post instances into serialized JSON for the client
posts: serializeModel(posts),
},
};
}

Deserializing

If your client-side code only reads from the server-side props and doesn't perform any updates to these models, then your client-side code won't need any changes.

However, if you receive models from the server and need to DataStore.delete(model) or DataStore.save(...) changes to them, you'll need the deserializeModel utility to convert them from server-friendly JSON back into model instances:

import { deserializeModel } from '@aws-amplify/datastore/ssr';
import { Amplify, withSSRContext } from "aws-amplify";
import { Post } from "../src/models";
export default function HomePage(initialData) {
// This converts the serialized JSON back into Post instances
const [posts, setPosts] = useState(deserializeModel(Post, initialData.posts));
...
}

Example:

Using the following schema:

type Post
@model
@auth(rules: [{ allow: owner }, { allow: public, operations: [read] }]) {
id: ID!
title: String!
content: String!
}

Open pages/index.js and replace it with the following code:

// pages/index.js
import Head from 'next/head';
import styles from '../styles/Home.module.css';
import {
Amplify,
Auth,
AuthModeStrategyType,
DataStore,
withSSRContext
} from 'aws-amplify';
import { Authenticator } from '@aws-amplify/ui-react';
import { Post } from '../src/models';
import { serializeModel } from '@aws-amplify/datastore/ssr';
import awsExports from '../src/aws-exports';
Amplify.configure({
...awsExports,
DataStore: {
authModeStrategyType: AuthModeStrategyType.MULTI_AUTH
},
ssr: true
});
export async function getServerSideProps({ req }) {
const SSR = withSSRContext({ req });
const posts = await SSR.DataStore.query(Post);
return {
props: {
// This converts Post instances into serialized JSON for the client
posts: serializeModel(posts)
}
};
}
async function handleCreatePost(event) {
event.preventDefault();
const form = new FormData(event.target);
try {
const post = await DataStore.save(
new Post({
title: form.get('title'),
content: form.get('content')
})
);
window.location.href = `/posts/${post.id}`;
} catch (error) {
console.log(error);
}
}
export default function Home({ posts = [] }) {
return (
<div className={styles.container}>
<Head>
<title>Amplify DataStore + Next.js</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Amplify DataStore + Next.js</h1>
<p className={styles.description}>
<code className={styles.code}>{posts.length}</code>
posts
</p>
<div className={styles.grid}>
{posts.map((post) => (
<a className={styles.card} href={`/posts/${post.id}`} key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</a>
))}
<div className={styles.card}>
<h3 className={styles.title}>New Post</h3>
<Authenticator>
<form onSubmit={handleCreatePost}>
<fieldset>
<legend>Title</legend>
<input
defaultValue={`Today, ${new Date().toLocaleTimeString()}`}
name="title"
/>
</fieldset>
<fieldset>
<legend>Content</legend>
<textarea
defaultValue="I built an Amplify project with Next.js and DataStore!"
name="content"
/>
</fieldset>
<button>Create Post</button>
<button type="button" onClick={() => Auth.signOut()}>
Sign out
</button>
</form>
</Authenticator>
</div>
</div>
</main>
</div>
);
}