Skip to content

Commit

Permalink
feat: add ssr migration guide
Browse files Browse the repository at this point in the history
  • Loading branch information
erinleigh90 committed Dec 6, 2023
1 parent 01296ba commit d61e076
Show file tree
Hide file tree
Showing 4 changed files with 744 additions and 357 deletions.
14 changes: 11 additions & 3 deletions src/directory/directory.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,19 @@ export const directory = {
},
{
path: 'src/pages/[platform]/build-a-backend/push-notifications/push-notifications-migration-guide/index.mdx'
},
}
]
},
{
path: 'src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx'
path: 'src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx',
children: [
{
path: 'src/pages/[platform]/build-a-backend/server-side-rendering/use-amplify-in-nextjs/index.mdx'
},
{
path: 'src/pages/[platform]/build-a-backend/server-side-rendering/nextjs-migration-guide/index.mdx'
}
]
},
{
path: 'src/pages/[platform]/build-a-backend/utilities/index.mdx',
Expand Down Expand Up @@ -664,7 +672,7 @@ export const directory = {
},
{
path: 'src/pages/[platform]/build-a-backend/more-features/in-app-messaging/in-app-messaging-migration-guide/index.mdx'
},
}
]
},
{
Expand Down
355 changes: 1 addition & 354 deletions src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,357 +30,4 @@ export function getStaticProps(context) {
};
}

This guide walks through how to use Amplify Auth, GraphQL API, REST API, and Storage category APIs from Next.js server-side runtimes.

import versionRangeCallout from '/src/fragments/lib/ssr/nextjs/version-range-callout.mdx';

<Fragments fragments={{ js: versionRangeCallout }} />

Before you begin, you will need:

- [A Next.js application created](/[platform]/start/project-setup/create-application/)
- [Installed Amplify libraries for Next.js](/[platform]/start/project-setup/create-application/#install-amplify-libraries)

## Configure Amplify Library for server-side usage

To use Amplify APIs on the server-side of your Next.js app, you will need to create a `runWithAmplifyServerContextRunner` function.

You can create an `amplifyServerUtils.ts` file under a `utils` folder in your codebase. In this file, you will import the Amplify configuration object from the `amplifyconfiguration.json` file that is generated by the Amplify CLI, and use the `createServerRunner` function to create the `runWithAmplifyServerContextRunner` function.

For example, the `utils/amplifyServerUtils.ts` file may contain the following content:

```typescript
import { createServerRunner } from '@aws-amplify/adapter-nextjs';
import config from '@/amplifyconfiguration.json';

export const { runWithAmplifyServerContext } = createServerRunner({
config
});
```

You can use the exported `runWithAmplifyServerContext` function to call Amplify APIs with in isolated request contexts. Usage examples see [here](#calling-amplify-category-apis-on-the-server-side).

**TIP:** You only need to call the `createServerRunner` function once and reuse the `runWithAmplifyServerContext` function throughout.

## Configure Amplify library for client-side usage

When you use the Amplify library on the client-side of your Next.js app, you will need to configure Amplify by calling the Amplify.configure as you would to use Amplify in a single-page application.

<Callout>

**NOTE:** To use the Amplify library on the client side in a Next.js app, you will need to set `ssr` to `true` when calling `Amplify.configure`. This instructs the Amplify library to store tokens in the cookie store of a browser. Cookies will be sent along with requests to your Next.js server for authentication.

</Callout>

```typescript
'use client';

import config from '@/amplifyconfiguration.json';
import { Amplify } from 'aws-amplify';

Amplify.configure(config, {
ssr: true // required when using Amplify with Next.js
});

export default function RootLayoutThatConfiguresAmplifyOnTheClient({
children
}: {
children: React.ReactNode;
}) {
return children;
}
```

<Callout>

To avoid repetitive calls to `Amplify.configure`, you can call it once in a top-level client-side rendered layout component.

</Callout>

<Accordion title='Configure Amplify in a Next.js App Router application' headingLevel='4' eyebrow='Learn more'>

If you're using the Next.js App Router, you can create a client component to configure Amplify and import it into your root layout.

`ConfigureAmplifyClientSide.ts`:

```typescript
'use client';

import { Amplify } from 'aws-amplify';
import config from '../amplifyconfiguration.json';

Amplify.configure(config, { ssr: true });

export default function ConfigureAmplifyClientSide() {
return null;
}
```

`layout.tsx`:

```jsx
import ConfigureAmplifyClientSide from '@/components/ConfigureAmplifyClientSide';
import './globals.css';

import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className="container pb-6">
<>
<ConfigureAmplifyClientSide />
{children}
</>
</body>
</html>
);
}
```

</Accordion>

## Authentication with Next.js server-side runtime

You can use the Amplify Auth category APIs to sign up and sign in your end users on the client side. With setting `ssr: true` when calling `Amplify.configure`, the Amplify library uses cookies to store tokens, which will be sent along with HTTP requests to your Next.js app server.

### Manage Auth session with the Next.js Middleware

You can use the `fetchAuthSession` API to check the auth sessions that are attached to the incoming requests in the middleware of your Next.js app to protect your routes. For example:

```typescript
import { fetchAuthSession } from 'aws-amplify/auth/server';
import { NextRequest, NextResponse } from 'next/server';
import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils';

export async function middleware(request: NextRequest) {
const response = NextResponse.next();

const authenticated = await runWithAmplifyServerContext({
nextServerContext: { request, response },
operation: async (contextSpec) => {
try {
const session = await fetchAuthSession(contextSpec);
return session.tokens !== undefined;
} catch (error) {
console.log(error);
return false;
}
}
});

if (authenticated) {
return response;
}

return NextResponse.redirect(new URL('/sign-in', request.url));
}

export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico|sign-in).*)'
]
};
```

In this example, if the incoming request is not associated with a valid user session the request will be redirected to the `/sign-in` route.

<Callout>

**NOTE:** When calling `fetchAuthSession` with a `response` context, it will send the refreshed tokens (if any) back to the client via the `Set-Cookie` header in the response.

</Callout>

## Calling Amplify category APIs on the server side

For the **Auth**, **REST APIs**, and **Storage** categories to use Amplify APIs on the server in your Next.js app, you will need to:

1. Import the API from the `/server` sub path.
2. Use the `runWithAmplifyServerContext` helper function created by calling the `createServerRunner` function exported from `@aws-amplify/adapter-nextjs` to call the Amplify API in an isolated server context.

For the **GraphQL API** category, review [Connect to GraphQL API from server-side runtimes](/[platform]/build-a-backend/graphqlapi/connect-from-server-runtime/).

<Callout>

**NOTE:** A subset of Amplify APIs can now be called on the server side of a Next.js app. These APIs are exported from the `/server` sub paths. See [the full list](#supported-apis-for-nextjs-server-side-usage) of supported APIs.

</Callout>

### With Next.js App Router

#### In React Server Component

##### Dynamic Rendering

Dynamic rendering is based on a user session extracted from an incoming request.

```jsx
import { cookies } from 'next/headers';
import { getCurrentUser } from '@aws-amplify/auth/server';
import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils';

// This page always dynamically renders per request
export const dynamic = 'force-dynamic';

export default async function AuthGetCurrentUserServer() {
try {
const currentUser = await runWithAmplifyServerContext({
nextServerContext: { cookies },
operation: (contextSpec) => getCurrentUser(contextSpec)
});

return (
<AuthFetchResult
description="The API is called on the server side."
data={currentUser}
/>
);
} catch (error) {
console.error(error);
return <p>Something went wrong...</p>;
}
}
```

##### Static Rendering

Static rendering doesn’t require a user session, so you can specify the `nextServerContext` parameter as `null`. This is useful for some use cases, for example, when you are using the Storage API with guest access (if you have enabled it in your backend).

```jsx
import { getUrl } from 'aws-amplify/storage/server';
import Image from 'next/image';
import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils';

// Re-render this page every 60 minutes
export const revalidate = 60 * 60; // in seconds

export default async function StaticallyRenderedPage() {
try {
const splashUrl = await runWithAmplifyServerContext({
nextServerContext: null,
operation: (contextSpec) =>
getUrl(contextSpec, {
key: 'splash.png'
})
});

return (
<Image
src={splashUrl.url.toString()}
alt="Splash Image"
width={500}
height={500}
/>
);
} catch (error) {
console.error(error);
return <p>Something went wrong...</p>;
}
}
```

<Callout>

**NOTE:** The URL returned by the `getUrl` API expires in the above example. You may want to specify the `revalidate` parameter to rerender the page as required to ensure the URL gets regenerated.

</Callout>

#### In Route Handlers

Take implementing an API route that enables `GET /apis/get-current-user`.

```typescript
import { getCurrentUser } from 'aws-amplify/auth/server';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils';

export async function GET() {
const user = await runWithAmplifyServerContext({
nextServerContext: { cookies },
operation: (contextSpec) => getCurrentUser(contextSpec)
});

return NextResponse.json({ user });
}
```

When you call `fetch('/apis/get-current-user')` it return a payload that contains the `user` data for the currently signed-in user.

### With Next.js Pages Router

#### In `getServerSideProps`

The following example extracts current user data from the request and provides them to a page react component via its props.

```typescript
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const currentUser = await runWithAmplifyServerContext({
nextServerContext: { request: req, response: res },
operation: (contextSpec) => getCurrentUser(contextSpec)
});

return { props: { currentUser } };
};
```

#### In `getStaticProps`

Similar to static rendering with the App Router, you can pass `null` as the value of the `nextServerContext` parameter to use the Amplify Storage API with guest access.

```typescript
export async function getStaticProps() {
const splashUrl = await runWithAmplifyServerContext({
nextServerContext: null,
operation: (contextSpec) => getUrl(contextSpec, { key: 'splash.png' })
});

return {
props: { imageUrl: splashUrl.url.toString() },
revalidate: (splashUrl.expiresAt.getTime() - Date.now()) / 1000 // in seconds
};
}
```

## Supported APIs for Next.js server-side usage

All APIs that support use on the server are exported from the `aws-amplify/<category>/server` sub paths. You **must** use these APIs for any server side use cases.

| Category | APIs | Server (Node.js) Amplify Hosting/Vercel | Vercel Edge Runtime (middleware) |
| --- | --- | --- | --- |
| Auth | `fetchAuthSession` |||
| Auth | `fetchUserAttributes` |||
| Auth | `getCurrentUser` |||
| API (GraphQL) | `generateServerClientUsingCookies` || |
| API (GraphQL) | `generateServerClientUsingReqRes` || |
| API (REST) | `GET` || |
| API (REST) | `POST` || |
| API (REST) | `PUT` || |
| API (REST) | `DEL` || |
| API (REST) | `HEAD` || |
| API (REST) | `PATCH` || |
| Storage | `getUrl` || |
| Storage | `getProperties` || |
| Storage | `list` || |
| Storage | `remove` || |
| Storage | `copy` || |

## Migrate from Amplify JavaScript v5

The Amplify JS v5 `withSSRContext` utility is no longer available with Amplify JS v6. You will need to use the `createServerRunner` function exported from `@aws-amplify/adapter-nextjs` to create a `runWithAmplifyServerContext` function, and use this function to make Amplify API calls on the server side of your Next.js app. For usage examples, see [here](#calling-amplify-category-apis-on-the-server-side).
<Overview childPageNodes={props.childPageNodes} />
Loading

0 comments on commit d61e076

Please sign in to comment.