Server-Side Rendering
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
:
1import { Amplify } from 'aws-amplify';2import awsExports from '../src/aws-exports';3
4Amplify.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/* runAmplify.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:
1import { Amplify, API } from "aws-amplify";2import awsExports from "../src/aws-exports";3
4Amplify.configure({ ...awsExports, ssr: true });5
6export default function HomePage({ posts = [] }) {7 const [posts, setPosts] = useState(posts);8
9 useEffect(() => {10 // Notice how the client correctly uses the top-level `API` import11 API.graphql({ query: listPosts }).then(({ data }) => setPosts(data.listPosts.items));12 }, [])13
14 return ( ... );15}
When on the server, use withSSRContext({ req?: ServerRequest })
:
1import { Amplify, API, withSSRContext } from 'aws-amplify';2
3export async function getServerSideProps({ req }) {4 // Notice how the server uses `API` from `withSSRContext`, instead of the top-level `API`.5 const SSR = withSSRContext({ req });6 const { data } = await SSR.API.graphql({ query: listPosts });7
8 return {9 props: {10 posts: data.listPosts.items11 }12 };13}
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
):
1'use client';2
3import { Amplify } from 'aws-amplify';4import awsExports from '@/aws-exports';5
6Amplify.configure({ ...awsExports, ssr: true });7
8export default function Home() {9 return {10 /* Render a login form for your users */11 };12}
Example Next.js App Router Server Component (e.g. app/posts/page.tsx
):
1import { Amplify } from 'aws-amplify';2import awsExports from '@/aws-exports';3
4Amplify.configure({ ...awsExports, ssr: true });5
6export default async function Page() {7 // ...8}
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
):
1import { Amplify, withSSRContext } from 'aws-amplify';2import { headers } from 'next/headers';3import { listPosts } from '@/graphql/queries';4import awsExports from '@/aws-exports';5
6Amplify.configure({ ...awsExports, ssr: true });7
8export default async function Page() {9 // Construct a req object & prepare an SSR enabled version of Amplify10 const req = {11 headers: {12 cookie: headers().get('cookie')13 }14 };15 const SSR = withSSRContext({ req });16
17 const { data } = await SSR.API.graphql({ query: listPosts });18
19 // ...20}
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:
1import { serializeModel } from '@aws-amplify/datastore/ssr';2import { Amplify, withSSRContext } from "aws-amplify";3
4...5
6export async function getServerSideProps({ req }) {7 const SSR = withSSRContext({ req });8 const posts = await SSR.DataStore.query(Post);9
10 return {11 props: {12 // This converts Post instances into serialized JSON for the client13 posts: serializeModel(posts),14 },15 };16}
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:
1import { deserializeModel } from '@aws-amplify/datastore/ssr';2import { Amplify, withSSRContext } from "aws-amplify";3
4import { Post } from "../src/models";5
6export default function HomePage(initialData) {7 // This converts the serialized JSON back into Post instances8 const [posts, setPosts] = useState(deserializeModel(Post, initialData.posts));9
10 ...11}
Example:
Using the following schema:
1type Post2 @model3 @auth(rules: [{ allow: owner }, { allow: public, operations: [read] }]) {4 id: ID!5 title: String!6 content: String!7}
Open pages/index.js and replace it with the following code:
1// pages/index.js2import Head from 'next/head';3import styles from '../styles/Home.module.css';4import {5 Amplify,6 Auth,7 AuthModeStrategyType,8 DataStore,9 withSSRContext10} from 'aws-amplify';11import { Authenticator } from '@aws-amplify/ui-react';12import { Post } from '../src/models';13import { serializeModel } from '@aws-amplify/datastore/ssr';14import awsExports from '../src/aws-exports';15
16Amplify.configure({17 ...awsExports,18 DataStore: {19 authModeStrategyType: AuthModeStrategyType.MULTI_AUTH20 },21 ssr: true22});23
24export async function getServerSideProps({ req }) {25 const SSR = withSSRContext({ req });26 const posts = await SSR.DataStore.query(Post);27
28 return {29 props: {30 // This converts Post instances into serialized JSON for the client31 posts: serializeModel(posts)32 }33 };34}35
36async function handleCreatePost(event) {37 event.preventDefault();38
39 const form = new FormData(event.target);40
41 try {42 const post = await DataStore.save(43 new Post({44 title: form.get('title'),45 content: form.get('content')46 })47 );48
49 window.location.href = `/posts/${post.id}`;50 } catch (error) {51 console.log(error);52 }53}54
55export default function Home({ posts = [] }) {56 return (57 <div className={styles.container}>58 <Head>59 <title>Amplify DataStore + Next.js</title>60 <link rel="icon" href="/favicon.ico" />61 </Head>62
63 <main className={styles.main}>64 <h1 className={styles.title}>Amplify DataStore + Next.js</h1>65 <p className={styles.description}>66 <code className={styles.code}>{posts.length}</code>67 posts68 </p>69
70 <div className={styles.grid}>71 {posts.map((post) => (72 <a className={styles.card} href={`/posts/${post.id}`} key={post.id}>73 <h3>{post.title}</h3>74 <p>{post.content}</p>75 </a>76 ))}77
78 <div className={styles.card}>79 <h3 className={styles.title}>New Post</h3>80
81 <Authenticator>82 <form onSubmit={handleCreatePost}>83 <fieldset>84 <legend>Title</legend>85 <input86 defaultValue={`Today, ${new Date().toLocaleTimeString()}`}87 name="title"88 />89 </fieldset>90
91 <fieldset>92 <legend>Content</legend>93 <textarea94 defaultValue="I built an Amplify project with Next.js and DataStore!"95 name="content"96 />97 </fieldset>98
99 <button>Create Post</button>100 <button type="button" onClick={() => Auth.signOut()}>101 Sign out102 </button>103 </form>104 </Authenticator>105 </div>106 </div>107 </main>108 </div>109 );110}