Skip to content

neokim/vendure-nextjs-storefront

 
 

Repository files navigation

Vendure NextJS Storefront

This is a NextJS starter for Vendure in the shape of a demo e-commerce shop. It's still in alpha, but feel free to read the concepts, run the store locally or just check out how it works at shop.aexol.com.

Installation

  1. Clone this repo via SSH, HTTPS or the GitHub CLI.
  2. Install packages using npm i
  3. Setup your vendure server locally and run it on http://localhost:3000/
  4. Create a .env file in the root of the project and add the following variables:
NEXT_PUBLIC_HOST="http://localhost:3000/shop-api"
  1. Then feel free to run it locally using npm run dev

Important

Reminder: you need to also have the Vendure store running locally to use this storefront

Tip

You can read about how to set that up in the Vendure Server section below

Table of contents

Vendure Server

This storefront requires a Vendure V2 server. You can either run a local instance, or use our public demo server.

Our demo of Vendure server (MinIO & Postgres & SMTP) can be found here to see all changes.

For the best experience when using our demo, you'll need to apply some ‘small’ modifications.

Here's a list of those small changes to the Vendure server:

  • apply two collections all and search. Both of them should contain all products (or not? for cases with gift cards / shipping-protections)
  • add the stock level as a number value and not as enum values
export class ExactStockDisplayStrategy implements StockDisplayStrategy {
  getStockLevel(
    ctx: RequestContext,
    productVariant: ProductVariant,
    saleableStockLevel: number
  ): string {
    return saleableStockLevel.toString();
  }
}

export const catalogOptions: VendureConfig["catalogOptions"] = {
  stockDisplayStrategy: new ExactStockDisplayStrategy(),
};

Zeus

We use GraphQL Zeus to provide Selectors for certain GraphQL query parts. You can think of Selectors as fragments in GraphQL, just with the added type-safety.

Page naming convention

In this starter, we're following a fairly simple naming convention for pages, that aligns with DDD (Domain-driven design) principles. Each page file is named using the format page-name.page.tsx, where page-name represents the name of the page or route. For example, the main page of your application would be named index.page.tsx. We're also using slug pages for products and collections, where we have a products and collections folder with a [slug].page.tsx where the [slug] is replaced by product or collection name fetched from the backend as props. This allows us to dynamically generate those pages at scale, while maintaining a simple to navigate structure with routes like /collections/electronics/ or /products/laptop/. Using this naming convention helps maintain a clean and organized folder structure that reflects the structure of your application's domains or features. By separating pages into their respective folders and adopting a consistent naming convention, you can easily locate and manage your application's routes and easily navigate any issues that might arise.

Internationalization with i18next

As most e-commerce shops use localization to reach clients who use different languages we have also added integrated i18next to handle translations. This makes it really easy to add and update translated content. Here's how we use i18next:

  1. Translation Files: We maintain separate JSON translation files for each supported language. These files contain translation keys and their corresponding localized text. For example, you might find the English translation file for home page at public/locales/en/homePage.json

  2. Locale Configuration: We configure i18next to load the appropriate translation files based on the user's selected locale.

  3. Integration with React: We use the next-i18next package to integrate i18next with React components, making it seamless to access translations in your React components via a simple useTranslation hook which will then always use the matching translation for the user's selected locale.

import { useTranslation } from 'next-i18next';

export const Home: React.FC = () => {
    const { t } = useTranslation('homepage');

    return (
        <Layout>
                <Hero
                    cta={t('hero-cta')}
                    h1={t('hero-h1')}
                    h2={t('hero-h2')}
                    desc={t('hero-p')}
                    link="/collections/all"
                />
        </Layout>
    );
};

Tip

For quick localization you can use DevTranslate to translate json files into up to 28 languages at the same time.

Icons

Lucide icons is an open source library which contains over one thousand svg icons and symbols separated into official packages, to make it easier to pick one for your project. Head on over to lucide.dev and check them out yourself.

Styles

We really like using Tailwind - that's why we are building our own engine based on styled components with props that work similarly to Tailwind. For example here's our Stack component:

export const Stack = styled.div<BaseFlexParams>`
    gap: ${p => p.gap || 0};
    display: flex;
    flex-direction: ${p => (p.column ? (p.reverse ? 'column-reverse' : 'column') : p.reverse ? 'row-reverse' : 'row')};
    flex-wrap: ${p => (p.flexWrap ? 'wrap' : 'nowrap')};
    justify-content: ${p =>
        p.justifyBetween ? 'space-between' : p.justifyCenter ? 'center' : p.justifyEnd ? 'end' : 'start'};
    align-items: ${p => (p.itemsCenter ? 'center' : 'initial')};
`;

With the props set up like that you can then use it almost like you would with Tailwind (just without ClassName):

<Stack column gap="2rem">
  {children}
</Stack>

Theme

Theming is provided by Emotion and some generic functions.

You can use values from the theme with thv which returns a function that consumes the theme and returns just the value or the usual method with ${p => p.theme}. You can see both uses in the example below:

import { thv } from '@/src/theme';
import styled from '@emotion/styled';

export const IconButton = styled.button<{ isActive?: boolean }>`
    color: ${thv.button.icon.front};
    border: 0;
    border-radius: 100%;
    font-weight: 600;
    outline: 0;
    width: 2.4rem;
    height: 2.4rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background: ${p => p.theme.button.icon.back || 'transparent'};
    svg {
        width: 2rem;
        height: 2rem;
    }
    :hover {
        box-shadow: none;
    }
`;

Useful Links

Who are the authors?

We are devs and contributors to the GraphQL ecosystem with a lot of experience and we want to enter Vendure to create developer-friendly e-commerce solutions that don't rely on clunky and outdated stuff like Shopify's Liquid wrapped with JavaScript.

Roadmap

  • Finish this starter
  • Deployment of the storefront connected to demo shop
  • Basic Cart functionality
  • Basic Checkout process
  • Design implementation
  • Basic Payment process
  • Basic User Profile
  • Search products
  • Filters
  • Localization with devtranslate.app
  • Adding Static Git CMS MDTX
  • Configure SEO and schema.org for every site
  • Assure ISR ready on every sub site
  • Migrate to the new next router

About

Storefront for vendure and NextJS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 99.3%
  • Other 0.7%