In this example we will look at how to deploy a full-stack Next.js app to your AWS account with OpenNext and the NextjsSite construct.

Requirements

Create a Next.js app

Change indicator Let’s start by creating a Next.js app. We’ll just go with the defaults.

$ npx create-next-app@latest

Initialize SST in your app

Change indicator Initialize SST in your Next.js app by running this in the root.

$ npx create-sst@latest
$ npm install

This will detect that you are trying to configure a Next.js app. It’ll add a sst.config.ts and a couple of packages to your package.json.

import { SSTConfig } from "sst";
import { NextjsSite } from "sst/constructs";

export default {
  config(_input) {
    return {
      name: "quickstart-nextjs",
      region: "us-east-1",
    };
  },
  stacks(app) {
    app.stack(function Site({ stack }) {
      const site = new NextjsSite(stack, "site");

      stack.addOutputs({
        SiteUrl: site.url,
      });
    });
  },
} satisfies SSTConfig;

The stacks code describes the infrastructure of your serverless app. SST uses AWS CDK.

You are ready to deploy your Next.js app at this point! But for the purpose of this example, we’ll go a bit further and add a file uploads feature to our app.

Start the dev environment

Change indicator Let’s start our SST dev environment.

$ npx sst dev

SST features a Live Lambda Development environment that allows you to work on your serverless apps live. This will ask you to start your Next.js dev environment as well.

Change indicator Start Next.js locally in a separate terminal.

$ npm run dev

This will run sst bind next dev. More on bind later.

Create our infrastructure

To support file uploads in our app, we need an S3 bucket. Let’s add that.

Add the table

Change indicator Add the following above our NextjsSite definition in the sst.config.ts.

const bucket = new Bucket(stack, "public");

Here we are using the Bucket construct to create an S3 bucket.

Change indicator Add it to the imports.

- import { NextjsSite } from "sst/constructs";
+ import { Bucket, NextjsSite } from "sst/constructs";

Bind it to our app

We want our Next.js app to be able to access our bucket.

Change indicator Add this to our Next.js definition in the sst.config.ts.

- const site = new NextjsSite(stack, "site");
+ const site = new NextjsSite(stack, "site", {
+   bind: [bucket],
+ });

We’ll see what bind does below.

Support file uploads

Now to let our users upload files in our Next.js app we need to start by generating a presigned URL. This is a temporary URL that our frontend can make a request to upload files.

Generate a presigned URL

Change indicator Add this to pages/index.ts above the Home component.

export async function getServerSideProps() {
  const command = new PutObjectCommand({
    ACL: "public-read",
    Key: crypto.randomUUID(),
    Bucket: Bucket.public.bucketName,
  });
  const url = await getSignedUrl(new S3Client({}), command);

  return { props: { url } };
}

This generates a presigned URL when our app loads. Note how we can access our S3 bucket in a typesafe way — Bucket.public.bucketName. You can learn more about Resource Binding over on our docs.

Change indicator We need to install a couple of packages.

$ npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

Change indicator And add these to the imports.

import crypto from "crypto";
import { Bucket } from "sst/node/bucket";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

Add an upload form

Now let’s add the form.

Change indicator Replace the Home component in pages/index.tsx with.

export default function Home({ url }: { url: string }) {
  return (
    <main>
      <form
        onSubmit={async (e) => {
          e.preventDefault();

          const file = (e.target as HTMLFormElement).file.files?.[0]!;

          const image = await fetch(url, {
            body: file,
            method: "PUT",
            headers: {
              "Content-Type": file.type,
              "Content-Disposition": `attachment; filename="${file.name}"`,
            },
          });

          window.location.href = image.url.split("?")[0];
        }}
      >
        <input name="file" type="file" accept="image/png, image/jpeg" />
        <button type="submit" className={inter.className}>
          Upload
        </button>
      </form>
    </main>
  );
}

Test your app

Now if you flip over to your browser, you should be able to upload an image and it’ll redirect to it!

Upload a file to S3 in Next.js app

Deploy to AWS

Change indicator To wrap things up we’ll deploy our app to prod.

$ npx sst deploy --stage prod

This allows us to separate our environments, so when we are working in our local environment, it doesn’t break the app for our users. You can stop the npm run dev command that we had previously run.

Once deployed, you should see your app’s URL.

SiteUrl: https://dq1n2yr6krqwr.cloudfront.net

If you head over to the URL in your browser, you should see your new Next.js app in action!

Deployed Next.js app with SST

We can add a custom domain to our app but we’ll leave that as an exercise for later.

Cleaning up

Finally, you can remove the resources created in this example using the following commands.

$ npx sst remove
$ npx sst remove --stage prod

Comparisons

In this example we looked at how to use SST to deploy a Next.js app to AWS. But there are a few different ways to deploy Next.js apps. Let’s look at how they all compare.

  • Vercel is the most popular way to deploy Next.js apps. It’s the most expensive and isn’t open source.

  • Amplify in many ways is AWS’s version of Vercel. It’s cheaper and deploys to your AWS account. But their implementation is incomplete and not on par with Vercel. And because they are not open source, you’ll need to file a support ticket to get your issues fixed.

  • Serverless Next.js (sls-next) Component is open source and deploys to your AWS account. But this project is not being maintained anymore.

  • SST is completely open source and deploys directly to your AWS account. It uses OpenNext — an open-source serverless adapter for Next.js. The OpenNext project is a community effort to reverse engineer Vercel’s implementation and make it available to everybody.

We hope this example has helped you deploy your Next.js apps to AWS. And given you an overview of all the deployment options out there.