docs
/
Example apps

Tutorial: the Next.js storefront

A working web storefront — catalog, product pages and a cart — with the application credentials kept off the browser by a thin server proxy.

nextjs-store

Get the code

Browse it on GitHub · Download the repository as a zip

git clone https://github.com/JacLight/appmint-examples.git
cd appmint-examples/nextjs-store

A small Next.js 15 storefront that renders your own catalog, shows a product page, and keeps a cart. It is the shape almost every web app on AppEngine should take, and it exists mostly to demonstrate one boundary properly.

The boundary it teaches

Browser  ──▶  this app's /api/*  ──▶  AppEngine

The browser never talks to AppEngine. It talks to this app's own origin, and the server relays the call with the application credentials attached.

That is not ceremony. Anything in browser JavaScript is readable by anyone who opens devtools, so an app token that reaches a bundle is an app token you have published. The comment at the top of lib/appmint.ts states the rule the whole file follows:

In the browser → calls this app's own /api/* proxy. No credentials. On the server → calls appengine directly, with the app token attached.

One client, two behaviours, decided by typeof window.

How it is laid out

FileWhat it does
lib/appmint.tsThe client. Fetches the app token once per process, reuses it, renews on a 401
app/api/[...slug]/route.tsThe proxy the browser talks to, with an allowlist
app/page.tsxThe catalog, rendered on the server
app/product/[sku]/page.tsxA product page
components/CartView.tsxThe cart
lib/cart-storage.tsCart kept in the browser between visits

The proxy is deliberately thin

app/api/[...slug]/route.ts turns GET /api/storefront/products into GET storefront/products against AppEngine, attaching the app token on the server side. It allows a fixed list of prefixes:

const ALLOWED = [
  'storefront/', 'profile/customer/', 'profile/magic-link',
  'affiliate/public/', 'client/', 'shipping/', 'notice/',
];

Its own comment is the important part:

It is deliberately thin — it moves credentials off the browser, it does not decide what a visitor may do. Guard anything sensitive in its own route rather than letting it fall through here.

Worth taking seriously. A proxy that forwards anything is a proxy that forwards the thing you did not think about. Anything scoped to a person, or that decides money, deserves a route of its own where your server decides what to ask.

Money comes from the server

The catalog renders prices exactly as AppEngine returns them, and totals come from the pricing endpoint rather than arithmetic in the page. A client that adds up its own subtotal is offering a second opinion, and the moment a price rule or tax rule resolves differently on the server, it quietly shows a number nobody will honour. See money is decided by the server.

Running it

cd appmint-examples/nextjs-store
cp .env.example .env.local     # fill in your org and app credentials
npm install
npm run dev                    # http://localhost:4100

.env.local is git-ignored; .env.example is the tracked template and holds placeholders only:

APPENGINE_ENDPOINT=https://appengine.appmint.io
ORG_ID=your-org-id
APP_ID=your-app-id
APP_KEY=your-app-key
APP_SECRET=your-app-secret

Those four values stay on the server. They are read in lib/appmint.ts through process.env, which in Next.js is server-only unless a variable is prefixed NEXT_PUBLIC_ — do not prefix these.

If the catalog page cannot reach AppEngine it says so on the page and points at .env.example, rather than rendering an empty grid that looks like you have no products.

What to take from it

The two-behaviour client. One module, honest about where it is running. It is far easier to keep credentials off the browser when the code physically cannot send them there.

An allowlist, not a pass-through. And sensitive things get their own route.

Server-rendered catalog. One round trip, real HTML, and no API surface named in the bundle.