Skip to content

Commit 03f6a24

Browse files
kevinoldreesscotcalebpollmanheatheramz
authored
feat: Implement Gen2 Next.js React Server Components (RSC) Quickstart (#6776)
* wip - cut page for app router server components * wip - iterate on app router server component quickstart * wip - iterate on middleware for RSC quickstart * wip - add amplify utils for server configuration * wip - finalize todos section * update to conditionally redirect if user * fix fragment path * add signout button to server components example * Import AuthUser to avoid TypeScript error Co-authored-by: Scott Rees <[email protected]> * add installation of amplify next.js adapter * Group imports into logical sections Co-authored-by: Caleb Pollman <[email protected]> * update additional section imports in logicial groups * add custom <Authenticator> accordion * revert signout functionality from server components guide * fix typo for AuthUser import * add separate Login component and update Login page * updates per internal feedback * updates to add a login page section * Clarify that Server Page implementation does not need the Login client component Co-authored-by: Heather Pundt <[email protected]> * Add link for Authenticator customization Co-authored-by: Scott Rees <[email protected]> --------- Co-authored-by: Scott Rees <[email protected]> Co-authored-by: Caleb Pollman <[email protected]> Co-authored-by: Heather Pundt <[email protected]>
1 parent b1531c2 commit 03f6a24

File tree

3 files changed

+357
-3
lines changed

3 files changed

+357
-3
lines changed

src/directory/directory.mjs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1940,7 +1940,10 @@ export const directory = {
19401940
path: 'src/pages/gen2/start/quickstart/nextjs-pages-router/index.mdx'
19411941
},
19421942
{
1943-
path: 'src/pages/gen2/start/quickstart/nextjs-app-router/index.mdx'
1943+
path: 'src/pages/gen2/start/quickstart/nextjs-app-router-client-components/index.mdx'
1944+
},
1945+
{
1946+
path: 'src/pages/gen2/start/quickstart/nextjs-app-router-server-components/index.mdx'
19441947
}
19451948
]
19461949
},

src/pages/gen2/start/quickstart/nextjs-app-router/index.mdx renamed to src/pages/gen2/start/quickstart/nextjs-app-router-client-components/index.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const meta = {
2-
title: 'Next.js App Router',
2+
title: 'Next.js App Router (Client Components)',
33
description: 'Get started with AWS Amplify (Gen 2) using the Next.js App Router.'
44
};
55

@@ -11,7 +11,7 @@ export function getStaticProps(context) {
1111
};
1212
}
1313

14-
This Quickstart guide will walk you through how to build a task list application with TypeScript, Next.js **App Router**, and React. If you are new to these technologies, we recommend you go through the official [React](https://react.dev/learn/tutorial-tic-tac-toe), [Next.js](https://nextjs.org/docs/getting-started/installation), and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) tutorials first.
14+
This Quickstart guide will walk you through how to build a task list application with TypeScript, Next.js **App Router with Client Components**, and React. If you are new to these technologies, we recommend you go through the official [React](https://react.dev/learn/tutorial-tic-tac-toe), [Next.js](https://nextjs.org/docs/getting-started/installation), and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) tutorials first.
1515

1616
import prerequisites from 'src/fragments/gen2/quickstart/prerequisites.mdx';
1717

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
export const meta = {
2+
title: 'Next.js App Router (Server Components)',
3+
description: 'Get started with AWS Amplify (Gen 2) using the Next.js App Router using Server Components.'
4+
};
5+
6+
export function getStaticProps(context) {
7+
return {
8+
props: {
9+
meta
10+
}
11+
};
12+
}
13+
14+
This Quickstart guide will walk you through how to build a task list application with TypeScript, Next.js **App Router with Server Components**, and React. If you are new to these technologies, we recommend you go through the official [React](https://react.dev/learn/tutorial-tic-tac-toe), [Next.js](https://nextjs.org/docs/getting-started/installation), and [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html) tutorials first.
15+
16+
import prerequisites from 'src/fragments/gen2/quickstart/prerequisites.mdx';
17+
18+
<Fragments fragments={{ javascript: prerequisites, nextjs: prerequisites }} />
19+
20+
import createProject from 'src/fragments/gen2/quickstart/create-nextjs-app-router-project.mdx';
21+
22+
<Fragments fragments={{ javascript: createProject, nextjs: createProject }} />
23+
24+
import buildABackend from 'src/fragments/gen2/quickstart/build-a-backend.mdx';
25+
26+
<Fragments fragments={{ javascript: buildABackend, nextjs: buildABackend }} />
27+
28+
29+
## Build UI
30+
31+
Let's add UI that connects to the backend data and auth resources.
32+
33+
34+
### Configure Amplify Client Side
35+
36+
First, install the Amplify UI component library:
37+
38+
```bash
39+
npm install @aws-amplify/ui-react
40+
```
41+
42+
Next, create a `components` folder in the root of your project and the contents below to a file called `ConfigureAmplify.tsx`.
43+
44+
```ts title="components/ConfigureAmplify.tsx"
45+
// components/ConfigureAmplify.tsx
46+
"use client";
47+
48+
import { Amplify } from "aws-amplify";
49+
50+
import config from "@/amplifyconfiguration.json";
51+
52+
Amplify.configure(config, { ssr: true });
53+
54+
export default function ConfigureAmplifyClientSide() {
55+
return null;
56+
}
57+
```
58+
59+
Update `app/layout.tsx` to import and render `<ConfigureAmplifyClientSide />`. This client component will configure Amplify for client pages in our application.
60+
61+
```ts title="app/layout.tsx"
62+
// app/layout.tsx
63+
import "@aws-amplify/ui-react/styles.css";
64+
import type { Metadata } from "next";
65+
import { Inter } from "next/font/google";
66+
import "./globals.css";
67+
68+
import ConfigureAmplifyClientSide from "@/components/ConfigureAmplify";
69+
70+
const inter = Inter({ subsets: ["latin"] });
71+
72+
export const metadata: Metadata = {
73+
title: "Create Next App",
74+
description: "Generated by create next app",
75+
};
76+
77+
export default function RootLayout({
78+
children,
79+
}: {
80+
children: React.ReactNode;
81+
}) {
82+
return (
83+
<html lang="en">
84+
<body className={inter.className}>
85+
<ConfigureAmplifyClientSide />
86+
{children}
87+
</body>
88+
</html>
89+
);
90+
}
91+
```
92+
93+
### Add a login page
94+
95+
First, create a client side Login component in the `components` folder that will be wrapped in `withAuthenticator`. If the user is logged in, they will be redirected to the index route, otherwise the [Amplify UI Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator) will be rendered.
96+
97+
```ts title="components/Login.tsx"
98+
// components/Login.tsx
99+
"use client";
100+
101+
import { withAuthenticator } from "@aws-amplify/ui-react";
102+
import { AuthUser } from "aws-amplify/auth";
103+
import { redirect } from "next/navigation";
104+
import { useEffect } from "react";
105+
106+
function Login({ user }: { user?: AuthUser }) {
107+
useEffect(() => {
108+
if (user) {
109+
redirect("/");
110+
}
111+
}, [user]);
112+
return null;
113+
}
114+
115+
export default withAuthenticator(Login);
116+
```
117+
118+
Next, create a new route under `app/login/page.tsx` to render the `Login` component.
119+
120+
```ts title="app/login/page.tsx"
121+
// app/login/page.tsx
122+
123+
import Login from "@/components/Login";
124+
125+
export default function LoginPage() {
126+
return <Login />;
127+
}
128+
```
129+
130+
<Accordion title="Custom <Authenticator> example">
131+
132+
Some applications require more customization for the `<Authenticator>` component. The following example shows how to add a custom Header to the `<Authenticator>`. For this use, you will not need the `Login` file in the `components` folder but can do everything through the `page` file in the `app/login/` folder. For more customization option, see [Authenticator Customization](https://ui.docs.amplify.aws/react/connected-components/authenticator/customization).
133+
134+
```ts title="app/login/page.tsx"
135+
// app/login/page.tsx - Custom <Authenticator>
136+
137+
"use client";
138+
139+
import {
140+
Authenticator,
141+
Text,
142+
View,
143+
useAuthenticator,
144+
} from "@aws-amplify/ui-react";
145+
import { redirect } from "next/navigation";
146+
import { useEffect } from "react";
147+
148+
const components = {
149+
Header() {
150+
return (
151+
<View textAlign="center">
152+
<Text><span style={{color: "white"}}>Authenticator Header</span></Text>
153+
</View>
154+
);
155+
},
156+
};
157+
158+
function CustomAuthenticator() {
159+
const { user } = useAuthenticator((context) => [context.user]);
160+
161+
useEffect(() => {
162+
if (user) {
163+
redirect("/");
164+
}
165+
}, [user]);
166+
167+
return <Authenticator components={components} />;
168+
}
169+
170+
export default function Login() {
171+
return (
172+
<Authenticator.Provider>
173+
<CustomAuthenticator />
174+
</Authenticator.Provider>
175+
);
176+
}
177+
178+
```
179+
180+
</Accordion>
181+
182+
183+
### Configure Amplify Server Side
184+
185+
First, install the Amplify Next.js Adapter:
186+
187+
```bash
188+
npm install @aws-amplify/adapter-nextjs
189+
```
190+
191+
Next, create a `utils/amplify-utils.ts` file from the root of the project and paste the code below. `runWithAmplifyServerContext` and `cookiesClient` are declared here and will be used to gain access to Amplify assets from the server.
192+
193+
194+
```ts title="utils/amplify-utils.ts"
195+
// utils/amplify-utils.ts
196+
import { cookies } from "next/headers";
197+
198+
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
199+
import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api";
200+
201+
import { type Schema } from "@/amplify/data/resource";
202+
import config from "@/amplifyconfiguration.json";
203+
204+
export const { runWithAmplifyServerContext } = createServerRunner({
205+
config,
206+
});
207+
208+
export const cookiesClient = generateServerClientUsingCookies<Schema>({
209+
config,
210+
cookies,
211+
});
212+
```
213+
214+
### Add middleware for server-side redirect
215+
216+
Create `middleware.ts` in the root of the project with the contents below.
217+
218+
This middleware runs `fetchAuthSession` wrapped in `runWithAmplifyServerContext` and will redirect to `/login` when a user is not logged in.
219+
220+
221+
```ts title="middleware.ts"
222+
// middleware.ts
223+
import { NextRequest, NextResponse } from "next/server";
224+
225+
import { fetchAuthSession } from "aws-amplify/auth/server";
226+
227+
import { runWithAmplifyServerContext } from "@/utils/amplify-utils";
228+
229+
export async function middleware(request: NextRequest) {
230+
const response = NextResponse.next();
231+
232+
const authenticated = await runWithAmplifyServerContext({
233+
nextServerContext: { request, response },
234+
operation: async (contextSpec) => {
235+
try {
236+
const session = await fetchAuthSession(contextSpec, {});
237+
return session.tokens !== undefined;
238+
} catch (error) {
239+
console.log(error);
240+
return false;
241+
}
242+
},
243+
});
244+
245+
if (authenticated) {
246+
return response;
247+
}
248+
249+
return NextResponse.redirect(new URL("/login", request.url));
250+
}
251+
252+
export const config = {
253+
matcher: [
254+
/*
255+
* Match all request paths except for the ones starting with:
256+
* - api (API routes)
257+
* - _next/static (static files)
258+
* - _next/image (image optimization files)
259+
* - favicon.ico (favicon file)
260+
* - login
261+
*/
262+
"/((?!api|_next/static|_next/image|favicon.ico|login).*)",
263+
],
264+
};
265+
```
266+
267+
Run your application with `npm run dev` and navigate to `http://localhost:3000`. You should now see the authenticator, which is already configured and ready for your first sign-up! Create a new user account, confirm the account through email, and then sign in.
268+
269+
### View list of to-do items
270+
271+
Now, let's display data on our app's frontend.
272+
273+
The code below uses the `cookiesClient` to provide access to the `Todo` model defined in the backend.
274+
275+
Modify your app's home page file, `app/page.tsx`, with the following code:
276+
277+
```ts title="app/page.tsx"
278+
// app/page.tsx
279+
280+
import { cookiesClient } from "@/utils/amplify-utils";
281+
282+
async function App() {
283+
const { data: todos } = await cookiesClient.models.Todo.list();
284+
285+
return (
286+
<>
287+
<h1>Hello, Amplify 👋</h1>
288+
<ul>
289+
{todos && todos.map((todo) => <li key={todo.id}>{todo.content}</li>)}
290+
</ul>
291+
</>
292+
);
293+
}
294+
295+
export default App;
296+
```
297+
298+
Once you save the file and navigate back to `http://localhost:3000`, you should see "Hello, Amplify" with a blank page for now because you have only an empty list of to-dos.
299+
300+
### Create a new to-do item
301+
302+
Let's update the component to have a form for prompting the user for the title for creating a new to-do list item and run the `addTodo` method on form submission. In a production app, the additional fields of the `Todo` model would be added to the form.
303+
304+
After creating a todo, `revalidatePath` is run to clear the Next.js cache for this route to instantly update the results from the server without a full page reload.
305+
306+
```ts title="app/page.tsx"
307+
// app/page.tsx
308+
309+
import { revalidatePath } from "next/cache";
310+
311+
import { cookiesClient } from "@/utils/amplify-utils";
312+
313+
async function App() {
314+
const { data: todos } = await cookiesClient.models.Todo.list();
315+
316+
async function addTodo(data: FormData) {
317+
"use server";
318+
const title = data.get("title") as string;
319+
await cookiesClient.models.Todo.create({
320+
content: title,
321+
done: false,
322+
priority: "medium",
323+
});
324+
revalidatePath("/");
325+
}
326+
327+
return (
328+
<>
329+
<h1>Hello, Amplify 👋</h1>
330+
<form action={addTodo}>
331+
<input type="text" name="title" />
332+
<button type="submit">Add Todo</button>
333+
</form>
334+
335+
<ul>
336+
{todos && todos.map((todo) => <li key={todo.id}>{todo.content}</li>)}
337+
</ul>
338+
</>
339+
);
340+
}
341+
342+
export default App;
343+
```
344+
345+
### Terminate dev server
346+
347+
Go to `localhost` in the browser to make sure you can now log in and create and list to-dos. You can end your development session by shutting down the frontend dev server and cloud sandbox. The sandbox prompts you to delete your backend resources. While you can retain your backend, we recommend deleting all resources so you can start clean again next time.
348+
349+
import deployAndHost from 'src/fragments/gen2/quickstart/deploy-and-host.mdx';
350+
351+
<Fragments fragments={{ javascript: deployAndHost, nextjs: deployAndHost }} />

0 commit comments

Comments
 (0)