diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs
index ef3a5e7fbc5..8a85bd6023f 100644
--- a/src/directory/directory.mjs
+++ b/src/directory/directory.mjs
@@ -397,6 +397,9 @@ export const directory = {
children: [
{
path: 'src/pages/[platform]/build-a-backend/server-side-rendering/nextjs-app-router-server-components/index.mdx'
+ },
+ {
+ path: 'src/pages/[platform]/build-a-backend/server-side-rendering/nuxt/index.mdx'
}
]
},
diff --git a/src/pages/[platform]/build-a-backend/server-side-rendering/nuxt/index.mdx b/src/pages/[platform]/build-a-backend/server-side-rendering/nuxt/index.mdx
new file mode 100644
index 00000000000..687f3d730c1
--- /dev/null
+++ b/src/pages/[platform]/build-a-backend/server-side-rendering/nuxt/index.mdx
@@ -0,0 +1,625 @@
+import { getCustomStaticPath } from '@/utils/getCustomStaticPath';
+import { getChildPageNodes } from '@/utils/getChildPageNodes';
+
+export const meta = {
+ title: 'Use Amplify categories APIs from Nuxt 3',
+ description: 'Use Amplify categories APIs from Nuxt 3',
+ platforms: [
+ 'javascript',
+ 'vue'
+ ],
+};
+
+export const getStaticPaths = async () => {
+ return getCustomStaticPath(meta.platforms);
+};
+
+export function getStaticProps(context) {
+ const childPageNodes = getChildPageNodes(meta.route);
+ return {
+ props: {
+ platform: context.params.platform,
+ meta,
+ childPageNodes
+ }
+ };
+}
+
+If you have not already done so, please read the introduction documentation, [Use Amplify Categories APIs in Server-Side Rendering](/gen1/[platform]/build-a-backend/server-side-rendering/), to learn about how to use Amplify categories' APIs in server-side rendering.
+
+This documentation provides a getting started guide to using the generic `runWithAmplifyServerContext` adapter (exported from `aws-amplify/adapter-core`) to enable Amplify in a Nuxt 3 project. The examples in this documentation may not present best practices for your Nuxt project. You are welcome to provide suggestions and contributions to improve this documentation or to create a Nuxt adapter package for Amplify and let others use it.
+
+
+
+**Note:** This guide assumes that you have deep knowledge of Nuxt 3.
+
+
+
+## Start using Amplify in your Nuxt 3 project
+
+You can install relevant Amplify libraries by following the [manual installation](/[platform]/start/manual-installation/) guide.
+
+## Set up the AmplifyAPIs plugin
+
+Nuxt 3 offers universal rendering by default, where your data fetching logic may be executed in both client and server runtimes. Amplify offers APIs that are capable of running within a server context to support use cases such as server-side rendering (SSR) and static site generation (SSG), though Amplify's client-side APIs and server-side APIs are slightly different. You can set up an `AmplifyAPIs` plugin to make your data fetching logic run smoothly across the client and server.
+
+1. If you haven’t already done so, create a `plugins` directory under the root of your Nuxt project
+2. Create two files `01.amplifyApis.client.ts` and `01.amplifyApis.server.ts` under the `plugins` directory
+
+
+
+**Note:** the leading number in the filenames indicates the plugin loading order, details see https://nuxt.com/docs/guide/directory-structure/plugins#registration-order. The `.client` and `.server` indicate the runtime that the logic contained in the file will run on, client or server. For details see: https://nuxt.com/docs/guide/directory-structure/plugins
+
+
+
+In these files, you will register both client-specific and server-specific Amplify APIs that you will use in your Nuxt project as a plugin. You can then access these APIs via the `useNuxtApp` composable.
+
+### Implement `01.amplifyApis.client.ts`
+
+Example implementation:
+
+```ts title="plugins/01.amplifyApis.client.ts"
+import type { Schema } from '~/amplify/data/resource';
+import { Amplify } from 'aws-amplify';
+import {
+ fetchAuthSession,
+ fetchUserAttributes,
+ signIn,
+ signOut
+} from 'aws-amplify/auth';
+import { list } from 'aws-amplify/storage';
+import { generateClient } from 'aws-amplify/api';
+import outputs from '../amplify_outputs.json';
+
+const client = generateClient();
+
+export default defineNuxtPlugin({
+ name: 'AmplifyAPIs',
+ enforce: 'pre',
+
+ setup() {
+ // This configures Amplify on the client side of your Nuxt app
+ Amplify.configure(config, { ssr: true });
+
+ return {
+ provide: {
+ // You can add the Amplify APIs that you will use on the client side
+ // of your Nuxt app here.
+ //
+ // You can call the API by via the composable `useNuxtApp()`. For example:
+ // `useNuxtApp().$Amplify.Auth.fetchAuthSession()`
+ Amplify: {
+ Auth: {
+ fetchAuthSession,
+ fetchUserAttributes,
+ signIn,
+ signOut
+ },
+ Storage: {
+ list
+ },
+ GraphQL: {
+ client
+ }
+ }
+ }
+ };
+ }
+});
+```
+
+
+
+Make sure you call `Amplify.configure` as early as possible in your application’s lifecycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue.
+
+
+
+### Implement `01.amplifyApis.server.ts`
+
+Example implementation:
+
+```ts title="plugins/01.amplifyApis.server.ts"
+import type { CookieRef } from 'nuxt/app';
+import type { Schema } from '~/amplify/data/resource';
+import type { ListPaginateWithPathInput } from 'aws-amplify/storage';
+import type {
+ LibraryOptions,
+ FetchAuthSessionOptions
+} from '@aws-amplify/core';
+import {
+ createKeyValueStorageFromCookieStorageAdapter,
+ createUserPoolsTokenProvider,
+ createAWSCredentialsAndIdentityIdProvider,
+ runWithAmplifyServerContext
+} from 'aws-amplify/adapter-core';
+import { parseAmplifyConfig } from 'aws-amplify/utils';
+import {
+ fetchAuthSession,
+ fetchUserAttributes,
+ getCurrentUser
+} from 'aws-amplify/auth/server';
+import { list } from 'aws-amplify/storage/server';
+import { generateClient } from 'aws-amplify/api/server';
+
+import outputs from '../amplify_outputs.json';
+
+// parse the content of `amplify_outputs.json` into the shape of ResourceConfig
+const amplifyConfig = parseAmplifyConfig(outputs);
+
+// create the Amplify used token cookies names array
+const userPoolClientId = amplifyConfig.Auth!.Cognito.userPoolClientId;
+const lastAuthUserCookieName = `CognitoIdentityServiceProvider.${userPoolClientId}.LastAuthUser`;
+
+// create a GraphQL client that can be used in a server context
+const gqlServerClient = generateClient({ config: amplifyConfig });
+
+// extract the model operation function types for creating wrapper function later
+type RemoveFirstParam = Params extends [infer _, ...infer Rest] ? Rest : never;
+type TodoListInput = RemoveFirstParam>;
+type TodoCreateInput = RemoveFirstParam>;
+type TodoUpdateInput = RemoveFirstParam>;
+
+const getAmplifyAuthKeys = (lastAuthUser: string) =>
+ ['idToken', 'accessToken', 'refreshToken', 'clockDrift']
+ .map(
+ (key) =>
+ `CognitoIdentityServiceProvider.${userPoolClientId}.${lastAuthUser}.${key}`
+ )
+ .concat(lastAuthUserCookieName);
+
+// define the plugin
+export default defineNuxtPlugin({
+ name: 'AmplifyAPIs',
+ enforce: 'pre',
+ setup() {
+ // The Nuxt composable `useCookie` is capable of sending cookies to the
+ // client via the `SetCookie` header. If the `expires` option is left empty,
+ // it sets a cookie as a session cookie. If you need to persist the cookie
+ // on the client side after your end user closes your Web app, you need to
+ // specify an `expires` value.
+ //
+ // We use 30 days here as an example (the default Cognito refreshToken
+ // expiration time).
+ const expires = new Date();
+ expires.setDate(expires.getDate() + 30);
+
+ // Get the last auth user cookie value
+ //
+ // We use `sameSite: 'lax'` in this example, which allows the cookie to be
+ // sent to your Nuxt server when your end user gets redirected to your Web
+ // app from a different domain. You should choose an appropriate value for
+ // your own use cases.
+ const lastAuthUserCookie = useCookie(lastAuthUserCookieName, {
+ sameSite: 'lax',
+ expires,
+ secure: true
+ });
+
+ // Get all Amplify auth token cookie names
+ const authKeys = lastAuthUserCookie.value
+ ? getAmplifyAuthKeys(lastAuthUserCookie.value)
+ : [];
+
+ // Create a key-value map of cookie name => cookie ref
+ //
+ // Using the composable `useCookie` here in the plugin setup prevents
+ // cross-request pollution.
+ const amplifyCookies = authKeys
+ .map((name) => ({
+ name,
+ cookieRef: useCookie(name, { sameSite: 'lax', expires, secure: true })
+ }))
+ .reduce>>(
+ (result, current) => ({
+ ...result,
+ [current.name]: current.cookieRef
+ }),
+ {}
+ );
+
+ // Create a key value storage based on the cookies
+ //
+ // This key value storage is responsible for providing Amplify Auth tokens to
+ // the APIs that you are calling.
+ //
+ // If you implement the `set` method, when Amplify needed to refresh the Auth
+ // tokens on the server side, the new tokens would be sent back to the client
+ // side via `SetCookie` header in the response. Otherwise the refresh tokens
+ // would not be propagate to the client side, and Amplify would refresh
+ // the tokens when needed on the client side.
+ //
+ // In addition, if you decide not to implement the `set` method, you don't
+ // need to pass any `CookieOptions` to the `useCookie` composable.
+ const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({
+ get(name) {
+ const cookieRef = amplifyCookies[name];
+
+ if (cookieRef && cookieRef.value) {
+ return { name, value: cookieRef.value };
+ }
+
+ return undefined;
+ },
+ getAll() {
+ return Object.entries(amplifyCookies).map(([name, cookieRef]) => {
+ return { name, value: cookieRef.value ?? undefined };
+ });
+ },
+ set(name, value) {
+ const cookieRef = amplifyCookies[name];
+ if (cookieRef) {
+ cookieRef.value = value;
+ }
+ },
+ delete(name) {
+ const cookieRef = amplifyCookies[name];
+
+ if (cookieRef) {
+ cookieRef.value = null;
+ }
+ }
+ });
+
+ // Create a token provider
+ const tokenProvider = createUserPoolsTokenProvider(
+ amplifyConfig.Auth!,
+ keyValueStorage
+ );
+
+ // Create a credentials provider
+ const credentialsProvider = createAWSCredentialsAndIdentityIdProvider(
+ amplifyConfig.Auth!,
+ keyValueStorage
+ );
+
+ // Create the libraryOptions object
+ const libraryOptions: LibraryOptions = {
+ Auth: {
+ tokenProvider,
+ credentialsProvider
+ }
+ };
+
+ return {
+ provide: {
+ // You can add the Amplify APIs that you will use on the server side of
+ // your Nuxt app here. You must only use the APIs exported from the
+ // `aws-amplify//server` subpaths.
+ //
+ // You can call the API by via the composable `useNuxtApp()`. For example:
+ // `useNuxtApp().$Amplify.Auth.fetchAuthSession()`
+ //
+ // Recall that Amplify server APIs are required to be called in a isolated
+ // server context that is created by the `runWithAmplifyServerContext`
+ // function.
+ Amplify: {
+ Auth: {
+ fetchAuthSession: (options: FetchAuthSessionOptions) =>
+ runWithAmplifyServerContext(
+ amplifyConfig,
+ libraryOptions,
+ (contextSpec) => fetchAuthSession(contextSpec, options)
+ ),
+ fetchUserAttributes: () =>
+ runWithAmplifyServerContext(
+ amplifyConfig,
+ libraryOptions,
+ (contextSpec) => fetchUserAttributes(contextSpec)
+ ),
+ getCurrentUser: () =>
+ runWithAmplifyServerContext(
+ amplifyConfig,
+ libraryOptions,
+ (contextSpec) => getCurrentUser(contextSpec)
+ )
+ },
+ Storage: {
+ list: (input: ListPaginateWithPathInput) =>
+ runWithAmplifyServerContext(
+ amplifyConfig,
+ libraryOptions,
+ (contextSpec) => list(contextSpec, input)
+ )
+ },
+ GraphQL: {
+ client: {
+ models: {
+ Todo: {
+ list(...input: TodoListInput) {
+ return runWithAmplifyServerContext(
+ amplifyConfig,
+ libraryOptions,
+ (contextSpec) => gqlServerClient.models.Todo.list(contextSpec, ...input)
+ )
+ },
+ create(...input: TodoCreateInput) {
+ return runWithAmplifyServerContext(
+ amplifyConfig,
+ libraryOptions,
+ (contextSpec) => gqlServerClient.models.Todo.create(contextSpec, ...input)
+ )
+ },
+ update(...input: TodoUpdateInput) {
+ return runWithAmplifyServerContext(
+ amplifyConfig,
+ libraryOptions,
+ (contextSpec) => gqlServerClient.models.Todo.update(contextSpec, ...input)
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ }
+});
+```
+
+#### Usage example
+
+Using the Storage `list` API in `pages/storage-list.vue`:
+
+```ts title="pages/storage-list.vue"
+
+// `useAsyncData` and `useNuxtApp` are Nuxt composables
+// `$Amplify` is generated by Nuxt according to the `provide` key in the plugins
+// we've added above
+
+
+
+ Files under path: album
+ {{ data }}
+
+```
+
+Using the GraphQL API in `pages/todos-list.vue`:
+
+```ts title="pages/todos-list.vue"
+
+
+
+ Todos
+ {{ data }}
+
+```
+
+The above two pages can be rendered on both the client and server by default. `useNuxtApp().$Amplify` will pick up the correct implementation of `01.amplifyApis.client.ts` and `01.amplifyApis.server.ts` to use, depending on the runtime.
+
+
+
+Only a subset of Amplify APIs are usable on the server side, and as the libraries evolve, `amplify-apis.client` and `amplify-apis.server` may diverge further. You can guard your API calls to ensure an API is available in the context where you use it (e.g., you can use `if (process.client)` to ensure that a client-only API isn't executed on the server).
+
+
+
+## Set up Auth middleware to protect your routes
+
+The auth middleware will use the plugin configured in the previous step as a dependency; therefore you can add the auth middleware via another plugin that will be loaded after the previous one.
+
+1. Create a `02.authRedirect.ts` file under plugins directory
+
+
+
+**Note:** This file will run on both client and server, details see: https://nuxt.com/docs/guide/directory-structure/middleware#when-middleware-runs. The `02` name prefix ensures this plugin loads after the previous so `useNuxtApp().$Amplify` becomes available.
+
+
+
+### Implement `02.authRedirect.ts`
+
+Example implementation:
+
+```ts title="plugins/02.authRedirect.ts"
+import { Amplify } from 'aws-amplify';
+import outputs from '~/amplify_outputs.json';
+
+// Amplify.configure() only needs to be called on the client side
+if (process.client) {
+ Amplify.configure(config, { ssr: true });
+}
+
+export default defineNuxtPlugin({
+ name: 'AmplifyAuthRedirect',
+ enforce: 'pre',
+ setup() {
+ addRouteMiddleware(
+ 'AmplifyAuthMiddleware',
+ defineNuxtRouteMiddleware(async (to) => {
+ try {
+ const session = await useNuxtApp().$Amplify.Auth.fetchAuthSession();
+
+ // If the request is not associated with a valid user session
+ // redirect to the `/sign-in` route.
+ // You can also add route match rules against `to.path`
+ if (session.tokens === undefined && to.path !== '/sign-in') {
+ return navigateTo('/sign-in');
+ }
+
+ if (session.tokens !== undefined && to.path === '/sign-in') {
+ return navigateTo('/');
+ }
+ } catch (e) {
+ if (to.path !== '/sign-in') {
+ return navigateTo('/sign-in');
+ }
+ }
+ }),
+ { global: true }
+ );
+ }
+});
+```
+
+
+
+Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue.
+
+
+
+## Set Up Amplify for API Route Use Cases
+
+Following the specification of Nuxt, your API route handlers will live under `~/server`, which is a separate environment from other parts of your Nuxt app; hence, the plugins created in the previous sections are not usable here, and extra work is required.
+
+### Set up Amplify server context utility
+
+1. If you haven’t already done so, create a `utils` directory under the server directory of your Nuxt project
+2. Create an `amplifyUtils.ts` file under the `utils` directory
+
+In this file, you will create a helper function to call Amplify APIs that are capable of running on the server side with context isolation.
+
+Example implementation:
+
+
+```ts title="utils/amplifyUtils.ts"
+import type { H3Event, EventHandlerRequest } from 'h3';
+import {
+ createKeyValueStorageFromCookieStorageAdapter,
+ createUserPoolsTokenProvider,
+ createAWSCredentialsAndIdentityIdProvider,
+ runWithAmplifyServerContext,
+ AmplifyServer,
+ CookieStorage
+} from 'aws-amplify/adapter-core';
+import { parseAmplifyConfig } from 'aws-amplify/utils';
+
+import type { LibraryOptions } from '@aws-amplify/core';
+import outputs from '~/amplify_outputs.json';
+
+const amplifyConfig = parseAmplifyConfig(config);
+
+const createCookieStorageAdapter = (
+ event: H3Event
+): CookieStorage.Adapter => {
+ // `parseCookies`, `setCookie` and `deleteCookie` are Nuxt provided functions
+ const readOnlyCookies = parseCookies(event);
+
+ return {
+ get(name) {
+ if (readOnlyCookies[name]) {
+ return { name, value: readOnlyCookies[name] };
+ }
+ },
+ set(name, value, options) {
+ setCookie(event, name, value, options);
+ },
+ delete(name) {
+ deleteCookie(event, name);
+ },
+ getAll() {
+ return Object.entries(readOnlyCookies).map(([name, value]) => {
+ return { name, value };
+ });
+ }
+ };
+};
+
+const createLibraryOptions = (
+ event: H3Event
+): LibraryOptions => {
+ const cookieStorage = createCookieStorageAdapter(event);
+ const keyValueStorage =
+ createKeyValueStorageFromCookieStorageAdapter(cookieStorage);
+ const tokenProvider = createUserPoolsTokenProvider(
+ amplifyConfig.Auth!,
+ keyValueStorage
+ );
+ const credentialsProvider = createAWSCredentialsAndIdentityIdProvider(
+ amplifyConfig.Auth!,
+ keyValueStorage
+ );
+
+ return {
+ Auth: {
+ tokenProvider,
+ credentialsProvider
+ }
+ };
+};
+
+export const runAmplifyApi = (
+ // we need the event object to create a context accordingly
+ event: H3Event,
+ operation: (
+ contextSpec: AmplifyServer.ContextSpec
+ ) => Result | Promise
+) => {
+ return runWithAmplifyServerContext(
+ amplifyConfig,
+ createLibraryOptions(event),
+ operation
+ );
+};
+```
+
+You can then use `runAmplifyApi` function to call Amplify APIs in an isolated server context.
+
+#### Usage example
+
+Take implementing an API route `GET /api/current-user` , in `server/api/current-user.ts`:
+
+```ts title="server/api/current-user.ts"
+import { getCurrentUser } from 'aws-amplify/auth/server';
+import { runAmplifyApi } from '~/server/utils/amplifyUtils';
+
+export default defineEventHandler(async (event) => {
+ const user = await runAmplifyApi(event, (contextSpec) =>
+ getCurrentUser(contextSpec)
+ );
+
+ return user;
+});
+```
+
+Then you can fetch data from this route, for example, `fetch('http://localhost:3000/api/current-user')`.
+
+## Set up server middleware to protect your API routes
+
+Similar to API routes, the previously added auth middleware are not usable under `/server`, hence extra work is required to set up a auth middleware to protect your routes.
+
+1. If you haven’t already done so, create a `middleware` directory under the `server` directory of your Nuxt project
+2. Create an `amplifyAuthMiddleware.ts` file under the `middleware` directory
+
+This middleware will be executed before a request reach your API route.
+
+Example implementation:
+
+```ts title="middleware/amplifyAuthMiddleware.ts"
+import { fetchAuthSession } from 'aws-amplify/auth/server';
+
+export default defineEventHandler(async (event) => {
+ if (event.path.startsWith('/api/')) {
+ try {
+ const session = await runAmplifyApi(event, (contextSpec) =>
+ fetchAuthSession(contextSpec)
+ );
+
+ // You can add extra logic to match the requested routes to apply
+ // the auth protection
+ if (session.tokens === undefined) {
+ setResponseStatus(event, 403);
+ return {
+ error: 'Access denied!'
+ };
+ }
+ } catch (error) {
+ return {
+ error: 'Access denied!'
+ };
+ }
+ }
+});
+```
+
+With this middleware, when executing `fetch('http://localhost:3000/api/current-user')` without signing in a user on the client side, the `fetch` will receive a 403 error, and the request won’t reach route `/api/current-user`.