# Next.js starter

> A complete headless store on the Next.js App Router: server components, a cart, checkout, buyer accounts and pages that refresh when you save.

Source: https://shopfa.st/docs/headless/nextjs

A complete store on the Next.js App Router, running on your ShopFast store through the
[Storefront API](https://shopfa.st/docs/headless/storefront-api) and the [SDK](https://shopfa.st/docs/headless/sdk). Server components fetch the catalog,
server actions run the cart and sign-in, and your store's own checkout takes the payment. Deploy it as it is, or make
it yours. The code is on [GitHub](https://github.com/anurag12340/shopfast-next-storefront) (MIT).

| Page | What it does |
| --- | --- |
| Home | The store's hero, collections, categories and featured products |
| Product | Gallery, options, live stock, reviews, recommendations, add to cart, Product JSON-LD |
| Collection, category | Sorting and "Show more" with cursor pagination |
| Search | Suggestions as you type, results with type filters and sorting |
| Cart | Quantities, discount codes and gift cards in one field |
| Checkout | Its own checkout page (your ways to pay, fields, questions, terms, tips and email codes), or the embedded or hosted checkout |
| Account | Sign-in with an emailed code, orders, license keys and downloads, wallet, subscriptions, tickets |
| Order | The receipt link (`/orders/<id>?access=…`) with keys and downloads once paid |
| Policies, support | Your policy pages and a support form |

It also has a sitemap, robots, and a `loading.tsx` on every route drawn from that page's own layout, so nothing moves
when the page arrives.

## Set up

1. Get the code and install it:

   ```sh
   npx degit anurag12340/shopfast-next-storefront my-store
   cd my-store
   npm install
   ```

2. In **Settings → Headless & MCP → Storefront keys**, create a **private** key (`sfsk_…`). The starter calls the API
   from its server only, so the key never reaches a browser.
3. Copy `.env.example` to `.env.local` and fill it in (see [Environment](#environment)).
4. Run it:

   ```sh
   npm run dev       # http://localhost:3000
   ```

5. In **Settings → Developers → Webhooks**, add an endpoint at `https://<your site>/api/revalidate` for the
   `storefront.updated` event and put its signing secret in `SHOPFAST_WEBHOOK_SECRET`. Pages then update seconds after
   you save a product; without it they refresh every 5 minutes.
6. Add your site's origin (`https://shop.example.com`, and `http://localhost:3000` while you develop) to the
   storefront key's allowed origins. Checkout sends buyers back only to a site listed there, and the embedded checkout
   opens only there.

## Environment

| Variable | |
| --- | --- |
| `SHOPFAST_SHOP` | Your store's handle (the part before `.shopfa.st`) or its `st_` id |
| `SHOPFAST_STOREFRONT_KEY` | The private storefront key (`sfsk_live_…`). Server only |
| `SHOPFAST_WEBHOOK_SECRET` | The signing secret of your `storefront.updated` endpoint (`whsec_…`) |
| `SITE_URL` | Where the site is served, for canonical links, Open Graph, the sitemap and the checkout's return pages |
| `SHOPFAST_CHECKOUT` | `custom` (the default: the site's own checkout page), `embedded` or `hosted` |
| `SHOPFAST_API_URL` | Leave it out: `https://api.shopfa.st` |

## How it fits together

`lib/shopfast.ts` makes two clients. The catalog client is shared by every buyer and keeps its answers in Next's Data
Cache with tags; the buyer client carries this buyer's address and session and is never cached:

```ts
import { createNextStorefrontClient } from '@shopfast-sdk/client/next';

export function catalog() {
  return createNextStorefrontClient({ shop: config.shop, key: config.key, revalidate: 300 });
}

export const buyer = cache(async () => {
  const [h, c] = await Promise.all([headers(), cookies()]);
  return catalog().with({ buyerIp: buyerIp(h), customerToken: c.get('sf_customer')?.value ?? null });
});
```

| File | What it does |
| --- | --- |
| `lib/shopfast.ts` | The two clients, and the cached reads every page shares |
| `app/actions/*.ts` | Server actions: the cart, sign-in, support, live stock and search suggestions |
| `components/BuyBox.tsx` | Options, quantity, add to cart and the live stock line |
| `components/EmbeddedCheckout.tsx` | Your store's checkout in an overlay on this site |
| `app/api/revalidate/route.ts` | The `storefront.updated` webhook |

The cart id and the buyer's session token live in httpOnly cookies. Writes go out from server actions, so a buyer
never talks to the API directly and your private key stays on the server.

### Pages that refresh when you save

Every cached answer carries the tag `shopfast:<shop>`, and product, collection and page answers carry their own
(`shopfast:<shop>:product:<handle>`…). When you change something, ShopFast sends
[`storefront.updated`](https://shopfa.st/docs/headless/revalidation) and the route refreshes exactly those tags:

```ts
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { handleRevalidationWebhook } from '@shopfast-sdk/client/next';

export function POST(request: Request) {
  return handleRevalidationWebhook(request, { secret: process.env.SHOPFAST_WEBHOOK_SECRET!, revalidateTag, shop: process.env.SHOPFAST_SHOP! });
}
```

### Checkout

`SHOPFAST_CHECKOUT` picks one of the three ways in [Headless checkout](https://shopfa.st/docs/headless/checkout):

- `custom`: `/checkout` is the site's own page, drawn from `cart.checkoutOptions`. It starts the checkout with
  `cart.checkout`, asks for the emailed code when the store wants one, and sends the buyer to `payUrl`. After paying,
  the buyer comes back to `/checkout/complete`, which keeps the order's access token in a cookie and opens the order.
- `embedded`: your store's checkout in an overlay on the cart page, opened with the same cart (`embed.cart`).
- `hosted`: a signed link to your store's checkout, back to the site after paying.

Payment methods, taxes, fraud checks and delivery are your store's own in all three.

## Deploy

Any host that runs Next.js works (Vercel, Netlify, a Node server). Set the variables above, then `npm run build`. The
buyer's address is read from the first `x-forwarded-for` hop, which is what Vercel and most proxies send; on your own
server, make sure your proxy sets it.
