← All posts

Deploying a Next.js Portfolio with AWS Amplify

November 20, 2025

This portfolio runs on AWS Amplify. Not the full Amplify framework — just Amplify Hosting, pointing at a Next.js app with a SAM-deployed serverless backend behind it. Getting everything wired up taught me a few things the docs don't spell out clearly.

Like the blob post, this started as an excuse to explore. I wanted to play with Next.js — it looked like a natural fit for a portfolio, the right balance of flexibility and structure. And once I had a Next.js app, deploying it to AWS Amplify was a good way to get hands-on with the service. Two birds, one portfolio.


What Amplify Hosting Actually Is

Amplify Hosting is a CI/CD and CDN service. You point it at a GitHub repo, it runs your build script on every push, and deploys the result. For a Next.js app with SSR it uses a managed serverless runtime to handle server-rendered pages alongside static assets.

It's not a full framework — it's closer to Vercel or Netlify than to something like Heroku. Once I understood that, the mental model clicked.


The Build Config

The build is defined in a config file at the repo root. A few things in there matter more than they look.

Pin your Node version explicitly. Amplify build images come with a version manager pre-installed, but the default Node is old. Without pinning, your build environment changes under you whenever Amplify updates its defaults. Locking to Node 20 via the version manager in the pre-build step makes the environment reproducible.

The base directory for SSR is non-obvious. Amplify needs to know where to find the server artifacts after the build. Without setting this correctly, the build succeeds but the deployed app returns 404 on every route — with no hint in the error message about what's wrong. This was the most frustrating trap I hit.

Build cache matters. Caching the dependencies folder and the Next.js build cache cuts most build times by more than half. Easy win, often skipped.


Custom Domain and CORS

The site uses a custom domain. One thing to get right: the backend Lambda needs to have the correct allowed origin before you deploy. I set it to the custom domain directly rather than the auto-generated Amplify URL — the domain was configured in the same session, so I deployed against the final URL and avoided having to redeploy the Lambda later just to fix CORS.

If you're wiring a separate backend to an Amplify-hosted frontend, set the final domain first, then deploy the backend.


What I'd Watch Out For

A successful build can still deploy a broken app. The base directory issue is a good example — everything builds cleanly, the deploy completes, and then every route 404s. The problem isn't in the build output, it's in how Amplify interprets it. When a deployment looks right but behaves wrong, check the Amplify-specific config before digging into the app itself.

SSR routes cost money. Static pages are served from the CDN for free. Server-rendered routes hit a compute runtime that charges per invocation. It's worth knowing which routes in your app actually need to be dynamic and pre-rendering everything that doesn't.


The Full Stack Together

The portfolio is Next.js on Amplify Hosting, with a SAM stack running separately that provides the backend APIs. The two are connected only by environment variables configured in the Amplify console — API endpoints and public client config the frontend needs at runtime.

Keeping hosting and backend deployment independent means a Lambda change doesn't require a frontend redeploy, and vice versa. It's a clean split that's worth designing for from the start.