Skip to content

matt-wigg/aws-amplify-next-js-clean-architecture

Repository files navigation

AWS Amplify Next.js Clean Architecture

This repository contains a Next.js application integrated with AWS Amplify, providing a robust backend with server-side rendering (SSR). It is designed as a sandbox environment for rapid prototyping and testing. The project follows the guidelines provided in the Amplify Next.js App Router with Server Components Guide and implements the latest Next.js v15 (React 19) features.

Image

Table of Contents

Features

  • Server-Side Rendering (SSR): Utilizes Next.js for fast, SEO-friendly pages.
  • AWS Amplify Integration: Simplifies backend management for authentication, APIs, and storage.
  • Sandbox Environment: Ideal for testing and development, with the ability to switch between multiple environments.
  • Extensible Architecture: Easily extend the app with additional backend services or frontend components.

Prerequisites

Before setting up the project, ensure you have the following installed:

  • Node.js: Download (runtime for JavaScript).
  • npm: Download (comes with Node.js).
  • Git: Download (to clone the repository).
  • AWS Account: Sign Up (for Amplify services).

Getting Started

  1. Clone the Repository

    git clone https://github.com/matt-wigg/aws-amplify-next-js-clean-architecture.git
    cd aws-amplify-next-js-clean-architecture
  2. Install Dependencies

    npm install

AWS Amplify Setup

Important

Before running the application, you need to set up AWS Amplify for local development. Follow the official AWS documentation for configuring your AWS account and setting up IAM Identity Center - Configure AWS for Local Development.

Amplify Sandbox Management

You can manage Amplify sandboxes in two ways:

1. Custom NPM Scripts (reccomended for local dev)

Located in infrastructure/amplify/scripts/, these scripts simplify common sandbox operations with interactive prompts.

Start a Sandbox (with prompts)
cd infrastructure
npm run amplify

This will:

  1. Prompt for your AWS profile (e.g. aws-dev-environment)
  2. Prompt for a sandbox identifier (optional)
Delete a Sandbox (with confirmation)
cd infrastructure
npm run amplify:delete

This will:

  1. Prompt for the same profile and optional identifier
  2. Ask for confirmation before deleting resources

2. Standard Amplify CLI Commands (Manual)

Use these when you need full control or automation in CI/CD pipelines.

Start a Sandbox (manually)
cd infrastructure
npx ampx sandbox --profile <your-profile-name> --identifier <optional-custom-id>
Delete a Sandbox (manually)
cd infrastructure
npx ampx sandbox delete --profile <your-profile-name> --identifier <optional-custom-id>

About Identifiers

By default, the sandbox --identifier is set to your system username. If you start a sandbox with a custom identifier, you must also delete it using the same identifier.

Local Development

Note

Please see the official Amplify documentation for more information on how to use cloud sandboxes in dev environments.

  1. Start the Next.js Development Server

    npm run dev

    or start with turbopack - a faster development server:

    npm run turbo-dev
  2. Start the Amplify Sandbox

    cd infrastructure
    npm run amplify
  3. Access the Application

    Your application will be available at http://localhost:3000.

Amplify Authenticator Component

This project utilizes the AWS Amplify Authenticator UI component to manage user authentication seamlessly. The Authenticator provides pre-built, customizable authentication flows, including sign-up, sign-in, and multi-factor authentication, reducing the need for extensive boilerplate code.

Customizing the Authenticator

Image

You can customize the Authenticator component by providing custom components for the header, footer, and other UI elements.

// @nextjs/components/auth/cognito-authenticator

"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { Authenticator, useAuthenticator } from "@aws-amplify/ui-react";
import Image from "next/image";
import Logo from "@public/logo.webp";
import { ROUTES } from "@nextjs/constants/routes.constants";

/**
 * Custom UI components for the Amplify Authenticator.
 * Includes Matt Wigg-branded header and a global footer.
 */
const customComponents = {
  Header() {
    return (
      <figure className="w-30 h-30 mx-auto border border-border relative overflow-hidden rounded-full mb-8">
        <Image
          src={Logo}
          alt="Matt Wigg Logo"
          fill
          sizes="96px"
          style={{ objectFit: "cover" }}
          priority
          quality={85}
        />
      </figure>
    );
  },
  Footer() {
    return (
      <p className="text-sm text-center text-muted-foreground mt-4">
        &copy; {new Date().getFullYear()} Matt Wigg. All Rights Reserved.
      </p>
    );
  },
};

/**
 * CognitoAuthenticator component.
 * Renders the Amplify Authenticator with Matt Wigg branding and handles routing after sign in.
 */
export function CognitoAuthenticator() {
  const { authStatus } = useAuthenticator((context) => [context.authStatus]);
  const router = useRouter();

  useEffect(() => {
    if (authStatus === "authenticated") {
      router.replace(ROUTES.INTERNAL.HOME);
    }
  }, [authStatus]);

  return <Authenticator components={customComponents} initialState="signIn" />;
}

Clean Architecture

This project follows Uncle Bob’s Clean Architecture principles to enforce a clear separation of concerns and maintain high scalability and testability.

Image

Key Concepts

Clean Architecture promotes the following layered structure:

  • Domain Layer (Entities & Interfaces):

    • Contains pure business logic and core models (e.g. Todo, User, etc.).
    • Defines interfaces for repositories, not implementations.
    • Independent of frameworks, databases, or UI.
  • Infrastructure Layer (Frameworks, Drivers):

    • Implementation details (e.g. AWS Amplify, cookies, API clients).
    • Injected into the system via interfaces, never accessed directly by use cases.
  • Application Layer (Use Cases):

    • Contains business use cases that orchestrate operations (e.g. createTodo, getCurrentUser).
    • Coordinates domain entities and repository interfaces.
    • No knowledge of external dependencies.
  • Interface Adapters (Controllers, Presenters):

    • Adapts data between the application layer and the outside world.
    • Includes controllers (input), presenters (output), and formatters.
    • Converts raw results into view-ready responses.

Example: Todo List Flow

The following example demonstrates how the getTodos functionality flows through all architectural layers:

1. Domain Layer (Entities & Interfaces)

// @domain/models/Todo.ts

type Todo = {
  id: string;
  content: string;
  order?: number;
  // ...other properties
}
// @domain/interfaces/todo.repository.interface.ts

interface ITodoRepository {
  list(): Promise<Todo[]>;
  // ...other methods
}

2. Infrastructure Layer (Frameworks & Drivers)

// @infrastructure/repositories/todo.repository.ts

import { cookiesClient } from "@infrastructure/utils/amplify.utils";
import type { ITodoRepository } from "@domain/repositories/todo.interface";

export const todoRepository: ITodoRepository = {
  async list(): Promise<Todo[]> {
    try {
      const { data } = await cookiesClient.models.Todo.list({});
      return data ?? [];
    } catch (err) {
      console.error("TodoRepository.list error", err);
      return [];
    }
  },
  // ...other methods
};

3. Application Layer (Use Cases)

// @application/use-cases/todo/get-todos.ts

import type { ITodoRepository } from "@domain/repositories/todo.interface";
import type { Todo } from "@domain/models/Todo";

export async function getTodos(repo: ITodoRepository): Promise<Todo[]> {
  return repo.list();
}

4. Interface Adapters (Controllers & Presenters)

// @interfaceadapters/controllers/todo/todo.controller.ts

import { getTodos } from "@application/use-cases/todo/get-todos";
import { todoRepository } from "@infrastructure/repositories/todo.repository";

export const TodoController = {
  async getTodos(): Promise<Todo[]> {
    return getTodos(todoRepository);
  },
  // ...other methods
};
// @interfaceadapters/presenters/todo/todo.presenter.ts

export const TodoPresenter = {
  presentSortedTodos(todos: Todo[]): Todo[] {
    if (todos.length === 0) {
      return [];
    }
    return [...todos].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
  },
};

5. UI Layer (Pages & Components)

// @nextjs/app/(authenticated)/draggable/page.tsx

"use server";

import { TodoController } from "@interface-adapters/controllers/todo/todo.controller";
import { TodoPresenter } from "@interface-adapters/presenters/todo/todo.presenter";
import { DraggableTodoList } from "@nextjs/components/draggable/draggable-todo-list";

export default async function DraggablePage() {
  // Controller retrieves data from use case
  const todos = await TodoController.getTodos();
  // Presenter formats data for UI consumption
  const sortedTodos = TodoPresenter.presentSortedTodos(todos);

  return (
    <main>
      {/* Components receive and display the prepared data */}
      <DraggableTodoList initialTodos={sortedTodos} />
    </main>
  );
}

Benefits

  • Testable: Core business logic is isolated and can be unit tested independently of infrastructure or UI.
  • Framework-Agnostic: You can replace AWS Amplify, Next.js, or UI libraries with minimal refactoring, thanks to clear abstractions.
  • Maintainable: Well-defined boundaries ensure that changes in one layer do not create unintended side effects in others.
  • Extensible: The modular structure makes it straightforward to add new features, integrations, or swap implementations as requirements evolve.

Resources

Deployment

Amplify Hosting

Amplify supports deployment and hosting for server-side rendered (SSR) web apps created using Next.js. Please see the Amplify documentation: Amplify support for Next.js.

Fullstack Branch Deployment

Amplify code-first DX (Gen 2) offers fullstack branch deployments that allow you to automatically deploy infrastructure and application code changes from feature branches. Fullstack Branch Deployments.

Troubleshooting

Issue Solution
Amplify CLI Issues Update with: npm install -g @aws-amplify/cli
Build Failures Ensure Node.js and npm versions match the project requirements.
Amplify Sandbox Issues Check Amplify Sandbox setup and AWS credentials.

Back to Top

About

A full-stack todo web app template built with Clean Architecture, Next.js, React 19 features, and AWS Amplify for auth, APIs, storage, and serverless functions.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •