import nextCallout from "/src/fragments/lib/ssr/js/next-js-callout.mdx";

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

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`:

```js
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](https://github.com/vercel/next.js/issues/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:

```js
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 })`:

```js
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
    }
  }
}
```

<Callout>

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.

</Callout>

## 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`):
```js
'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`):

```js
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`):

```js
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:

```js
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_:

```js
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:

```graphql
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:

```jsx
// 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>
  );
}
```
