├── .env.example ├── .eslintrc.js ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .vscode ├── launch.json └── settings.json ├── README.md ├── app ├── (checkout) │ ├── cart │ │ └── page.tsx │ └── checkout │ │ ├── information │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── payment │ │ └── page.tsx │ │ ├── place-order │ │ └── page.tsx │ │ └── shipping │ │ └── page.tsx ├── (customer) │ └── customer │ │ ├── forget-password │ │ └── page.tsx │ │ ├── login │ │ └── page.tsx │ │ └── register │ │ └── page.tsx ├── [page] │ ├── layout.tsx │ ├── opengraph-image.tsx │ └── page.tsx ├── api │ ├── auth │ │ └── [...nextauth] │ │ │ └── route.ts │ └── revalidate │ │ └── route.ts ├── context │ └── store.tsx ├── error.tsx ├── favicon.ico ├── globals.css ├── icon.png ├── layout.tsx ├── next-auth-provider.tsx ├── opengraph-image.tsx ├── page.tsx ├── product │ └── [handle] │ │ ├── layout.tsx │ │ └── page.tsx ├── providers.tsx ├── robots.ts ├── search │ ├── [collection] │ │ ├── opengraph-image.tsx │ │ └── page.tsx │ ├── layout.tsx │ ├── loading.tsx │ └── page.tsx └── sitemap.ts ├── auth.ts ├── changelog.md ├── components ├── carousel.tsx ├── cart │ ├── actions.ts │ ├── add-to-cart.tsx │ ├── close-cart.tsx │ ├── delete-item-button.tsx │ ├── edit-item-quantity-button.tsx │ ├── index.tsx │ ├── modal.tsx │ └── open-cart.tsx ├── checkout │ ├── action.ts │ ├── cart │ │ ├── actions.ts │ │ ├── cart-item-accordian.tsx │ │ ├── cart.tsx │ │ ├── empty-cart.tsx │ │ ├── event-button.tsx │ │ ├── input.tsx │ │ ├── order-detail.tsx │ │ └── proceed-to-checkout.tsx │ ├── information │ │ └── checkout-form.tsx │ ├── loading.tsx │ ├── next-breadcrumb.tsx │ ├── payment │ │ └── index.tsx │ ├── place-holder.tsx │ ├── place-order │ │ └── index.tsx │ ├── region-drop-down.tsx │ ├── select-box.tsx │ └── shipping │ │ └── index.tsx ├── customer │ ├── index.tsx │ ├── lib │ │ └── action.ts │ ├── login │ │ ├── forget-password.tsx │ │ ├── loading-button.tsx │ │ ├── login-form.tsx │ │ └── registration-form.tsx │ ├── modal.tsx │ └── open-auth.tsx ├── grid │ ├── index.tsx │ ├── three-items.tsx │ └── tile.tsx ├── icons │ ├── check-sign.tsx │ ├── logo.tsx │ ├── right-arrow.tsx │ ├── seprator.tsx │ ├── shopping-cart.tsx │ └── wallet-logo.tsx ├── label.tsx ├── layout │ ├── footer-menu.tsx │ ├── footer.tsx │ ├── navbar │ │ ├── index.tsx │ │ ├── mobile-menu.tsx │ │ └── search.tsx │ ├── product-grid-items.tsx │ └── search │ │ ├── collections.tsx │ │ └── filter │ │ ├── dropdown.tsx │ │ ├── index.tsx │ │ └── item.tsx ├── loading-dots.tsx ├── logo-square.tsx ├── opengraph-image.tsx ├── price.tsx ├── product │ ├── gallery.tsx │ ├── product-description.tsx │ └── variant-selector.tsx └── prose.tsx ├── fonts └── Inter-Bold.ttf ├── lib ├── bagisto │ ├── fragments │ │ ├── cart.ts │ │ ├── image.ts │ │ ├── product.ts │ │ └── seo.ts │ ├── index.ts │ ├── mutations │ │ ├── cart.ts │ │ ├── customer-login.ts │ │ ├── customer-register.ts │ │ ├── payment-method.ts │ │ ├── place-order.ts │ │ ├── recover-password.ts │ │ ├── shipping-address.ts │ │ └── shipping-method.ts │ ├── queries │ │ ├── cart.ts │ │ ├── channel.ts │ │ ├── collection.ts │ │ ├── filter-attribute.ts │ │ ├── menu.ts │ │ ├── page.ts │ │ ├── payment-methods.ts │ │ ├── product.ts │ │ └── shipping-method.ts │ └── types.ts ├── constants.ts ├── type-guards.ts └── utils.ts ├── license.md ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── public └── image │ └── placeholder.webp ├── tailwind.config.js ├── tsconfig.json ├── types └── next-auth.d.ts └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | COMPANY_NAME="Vercel Inc." 2 | TWITTER_CREATOR="@vercel" 3 | TWITTER_SITE="https://nextjs.org/commerce" 4 | SITE_NAME="Next.js Commerce" 5 | BAGISTO_REVALIDATION_SECRET="" 6 | BAGISTO_STOREFRONT_ACCESS_TOKEN="" 7 | BAGISTO_PROTOCOL="" 8 | BAGISTO_STORE_DOMAIN="[your-bagisto-store-subdomain].mybagisto.com" 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['next', 'prettier'], 3 | plugins: ['unicorn'], 4 | rules: { 5 | 'no-unused-vars': [ 6 | 'error', 7 | { 8 | args: 'after-used', 9 | caughtErrors: 'none', 10 | ignoreRestSiblings: true, 11 | vars: 'all' 12 | } 13 | ], 14 | 'prefer-const': 'error', 15 | 'react-hooks/exhaustive-deps': 'error', 16 | 'unicorn/filename-case': [ 17 | 'error', 18 | { 19 | case: 'kebabCase' 20 | } 21 | ] 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | .playwright 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | .pnpm-debug.log* 28 | 29 | # local env files 30 | .env* 31 | !.env.example 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | .next 3 | pnpm-lock.yaml 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Next.js: debug server-side", 6 | "type": "node-terminal", 7 | "request": "launch", 8 | "command": "pnpm dev" 9 | }, 10 | { 11 | "name": "Next.js: debug client-side", 12 | "type": "chrome", 13 | "request": "launch", 14 | "url": "http://localhost:3000" 15 | }, 16 | { 17 | "name": "Next.js: debug full stack", 18 | "type": "node-terminal", 19 | "request": "launch", 20 | "command": "pnpm dev", 21 | "serverReadyAction": { 22 | "pattern": "started server on .+, url: (https?://.+)", 23 | "uriFormat": "%s", 24 | "action": "debugWithChrome" 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll": "explicit", 6 | "source.organizeImports": "explicit", 7 | "source.sortMembers": "explicit" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js Commerce Bagisto 2 | 3 | A Next.js 14 and App Router-ready ecommerce template featuring: 4 | 5 | - Next.js App Router 6 | - Optimized for SEO using Next.js's Metadata 7 | - React Server Components (RSCs) and Suspense 8 | - Server Actions for mutations 9 | - Edge Runtime 10 | - New fetching and caching paradigms 11 | - Dynamic OG images 12 | - Styling with Tailwind CSS 13 | - Checkout and payments with Bagisto 14 | - Automatic light/dark mode based on system settings 15 | 16 | Demo live at: [Bagisto NextJs Commerce](https://v2-bagisto-demo.vercel.app) 17 | 18 | ## Configuration 19 | 20 | ### Setup Bagisto Store 21 | 22 | - For `BAGISTO_PROTOCOL`, `BAGISTO_STOREFRONT_ACCESS_TOKEN`, `BAGISTO_REVALIDATION_SECRET` and `BAGISTO_STORE_DOMAIN`, you need to install the [Bagisto](https://github.com/bagisto/bagisto). 23 | - Then, you need to install the [Bagisto Headless Extension](https://github.com/bagisto/headless-ecommerce) in the Bagisto. 24 | - Now you need to host the full application so that you have store endpoint and if you are in development mode then you can use Ngrok also. 25 | - After that you can proceed with setting up Next.js commerce. 26 | 27 | ## Running locally 28 | 29 | You will need to use the environment variables [defined in `.env.example`](.env.example) to run Next.js Commerce. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/concepts/projects/environment-variables) for this, but a `.env` file is all that is necessary. 30 | 31 | > Note: You should not commit your `.env` file or it will expose secrets that will allow others to control your Bagisto store. 32 | 33 | 1. Install Vercel CLI: `npm i -g vercel` 34 | 2. Link local instance with Vercel and GitHub accounts (creates `.vercel` directory): `vercel link` 35 | 3. Download your environment variables: `vercel env pull` 36 | 37 | ```bash 38 | pnpm install 39 | pnpm dev 40 | ``` 41 | 42 | Your app should now be running on [localhost:3000](http://localhost:3000/). 43 | -------------------------------------------------------------------------------- /app/(checkout)/cart/page.tsx: -------------------------------------------------------------------------------- 1 | import EmptyCartPage from 'components/checkout/cart/empty-cart'; 2 | const CartPage = () => { 3 | return ; 4 | }; 5 | 6 | export default CartPage; 7 | -------------------------------------------------------------------------------- /app/(checkout)/checkout/information/page.tsx: -------------------------------------------------------------------------------- 1 | import FormPlaceHolder from 'components/checkout/place-holder'; 2 | import { getCart, getCountryList } from 'lib/bagisto'; 3 | import { Metadata } from 'next'; 4 | import dynamic from 'next/dynamic'; 5 | const GuestCheckOutForm = dynamic(() => import('components/checkout/information/checkout-form'), { 6 | loading: () => , 7 | ssr: false 8 | }); 9 | export default async function Information() { 10 | const countryList = await getCountryList(); 11 | const cart = await getCart(); 12 | return ; 13 | } 14 | export const metadata: Metadata = { 15 | title: 'Checkout', 16 | description: 'Checkout with store items' 17 | }; 18 | -------------------------------------------------------------------------------- /app/(checkout)/checkout/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Divider } from '@heroui/divider'; 2 | import Cart from 'components/checkout/cart/cart'; 3 | import Loading from 'components/checkout/loading'; 4 | import NextBreadcrumb from 'components/checkout/next-breadcrumb'; 5 | import FormPlaceHolder from 'components/checkout/place-holder'; 6 | import LogoSquare from 'components/logo-square'; 7 | import Link from 'next/link'; 8 | import { Suspense } from 'react'; 9 | const { SITE_NAME } = process.env; 10 | export default async function CheckoutLayout({ children }: React.PropsWithChildren) { 11 | return ( 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 23 | 24 | {SITE_NAME} 25 | 26 |
27 |
28 | 29 |
30 | }> {children} 31 |
32 | 33 |

34 | All rights reserved Dev Vercel Shop. 35 |

36 |
37 |
38 | }> 39 | 40 | 41 |
42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /app/(checkout)/checkout/payment/page.tsx: -------------------------------------------------------------------------------- 1 | import FormPlaceHolder from 'components/checkout/place-holder'; 2 | import { getCart, getPaymentMethod } from 'lib/bagisto'; 3 | 4 | import dynamic from 'next/dynamic'; 5 | 6 | const PaymentPage = dynamic(() => import('components/checkout/payment'), { 7 | loading: () => , 8 | ssr: false 9 | }); 10 | const payment = async () => { 11 | const cart = await getCart(); 12 | 13 | const methods = await getPaymentMethod({ 14 | shippingMethod: cart?.selectedShippingRate?.method || '' 15 | }); 16 | return ( 17 | 23 | ); 24 | }; 25 | 26 | export default payment; 27 | -------------------------------------------------------------------------------- /app/(checkout)/checkout/place-order/page.tsx: -------------------------------------------------------------------------------- 1 | import { getCart } from 'lib/bagisto'; 2 | import dynamic from 'next/dynamic'; 3 | const PlaceOrderPage = dynamic(() => import('components/checkout/place-order'), { 4 | ssr: false 5 | }); 6 | export default async function palaceOrder() { 7 | const cart = await getCart(); 8 | return ( 9 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /app/(checkout)/checkout/shipping/page.tsx: -------------------------------------------------------------------------------- 1 | import FormPlaceHolder from 'components/checkout/place-holder'; 2 | import { getCart, getShippingMethod } from 'lib/bagisto'; 3 | import type { Metadata } from 'next'; 4 | import dynamic from 'next/dynamic'; 5 | const ShippingMethod = dynamic(() => import('components/checkout/shipping'), { 6 | loading: () => , 7 | ssr: false 8 | }); 9 | const Shipping = async () => { 10 | const cart = await getCart(); 11 | const shippingMethod = await getShippingMethod(); 12 | 13 | return ; 14 | }; 15 | export default Shipping; 16 | export const metadata: Metadata = { 17 | title: 'Checkout', 18 | description: 'Checkout with store items' 19 | }; 20 | -------------------------------------------------------------------------------- /app/(customer)/customer/forget-password/page.tsx: -------------------------------------------------------------------------------- 1 | import { ForgetPasswordForm } from 'components/customer/login/forget-password'; 2 | import Link from 'next/link'; 3 | import LogoSquare from '../../../../components/logo-square'; 4 | 5 | export const metadata = { 6 | title: 'Search', 7 | description: 'Search for products in the store.' 8 | }; 9 | const { SITE_NAME } = process.env; 10 | export default function ForgetPasswordPage() { 11 | return ( 12 |
13 |
14 |
15 | 19 | 20 | {SITE_NAME} 21 | 22 |
23 |
24 | 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /app/(customer)/customer/login/page.tsx: -------------------------------------------------------------------------------- 1 | import { LoginForm } from 'components/customer/login/login-form'; 2 | import Link from 'next/link'; 3 | import LogoSquare from '../../../../components/logo-square'; 4 | 5 | export const metadata = { title: 'Search', description: 'Search for products in the store.' }; 6 | const { SITE_NAME } = process.env; 7 | 8 | export default async function LoginPage() { 9 | return ( 10 |
11 |
12 |
13 | 17 | 18 | {SITE_NAME} 19 | 20 |
21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /app/(customer)/customer/register/page.tsx: -------------------------------------------------------------------------------- 1 | import RegistrationForm from 'components/customer/login/registration-form'; 2 | import LogoSquare from 'components/logo-square'; 3 | import Link from 'next/link'; 4 | 5 | export const metadata = { 6 | title: 'Registration Form', 7 | description: 'Customer registration page' 8 | }; 9 | const { SITE_NAME } = process.env; 10 | export default async function Register() { 11 | return ( 12 |
13 |
14 |
15 | 19 | 20 | {SITE_NAME} 21 | 22 |
23 |
24 | 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /app/[page]/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from 'components/layout/footer'; 2 | import Navbar from 'components/layout/navbar'; 3 | 4 | export default function SearchLayout({ children }: { children: React.ReactNode }) { 5 | return ( 6 | <> 7 |
8 | 9 |
{children}
10 |
11 |