Horizon is a reference example and starter project for building custom Next v12 storefronts powered by Swell.js. The app is provided in an 'as-is' state and will not be receiving updates or bug fixes from March 1, 2024. Swell.js itself is in active development and is recommended SDK for building full-stack JS storefronts on Swell.

  • Built with Next.js and React
  • Uses TailwindCSS, Radix UI and Headless UI for accessible components
  • TypeScript and Storybook
  • Uses Swell’s GraphQL API
  • Includes page templates for subscriptions, single purchase, or both.
  • Includes extendable content blocks collection
  • Pre-built cart and checkout
  • Mixed physical and digital products
  • Customizable quizzes
  • Multi-currency support
  • i18n
  • Discounts & coupons
  • Product search
  • Product filters by attribute
  • Flexible product customization through variant options
  • Customer portal with orders and subscription handling

If you aren't already familiar with React, Next, Storybook, or Swell, these resources will get you up to speed:

Clone this project repository to your local machine.

The first step will be to set up the environment variables. Create an .env file at the root of the project's directory and add the following entries:

NEXT_PUBLIC_SWELL_STORE_URL: This endpoint should have the following format: https://{YOUR_STORE_URL}.swell.store

NEXT_PUBLIC_SWELL_PUBLIC_KEY: This value refers to a store's public key that can be found inside the Developer > API Keys route in the admin dashboard.

SWELL_STORE_ID: The store's ID. It can be found at the top of the Developer > API Keys route in the admin dashboard.

SWELL_SECRET_KEY: A store's secret key. It can be found inside the Developer > API Keys route in the Admin Dashboard, and it's used to sync the store with the local editor configuration.

SWELL_STOREFRONT_ID: The storefront id you are using Horizon with. It can be found at the top of the Storefronts > All storefronts > your storefront > Developer route in the admin Dashboard.

NEXT_PUBLIC_SWELL_EDITOR: This should be set to true to allow for correct functionality when using the swell editor. In a production environment, you can safely set this to false.

The next step will be setting up the development environment. We recommend installing Node.js through nvm. Afterward, run the following commands to match the project's Node and NPM versions and install the project's dependencies:

npm install
nvm install 
npm install -g npm@8 
npm install

This will automatically set up the GraphQL client with end-to-end typings and download the content and settings from your store's editor. Check out the graphql:generate and theme:generate commands on the package.json file for more information.

Lastly, you can start the development server with:

npm run dev
npm run dev

Horizon should now be running on http://localhost:3000 by default.

Once you've followed the steps in Deploy to Vercel. Add the storefront URL to the dashboard by selecting: Storefronts > All storefronts > Actions > edit settings > Storefront URL > Save changes.

You can take a look at the available components and the props they accept by running the storybook npm script:

npm run storybook
npm run storybook

Storybook should now be running on http://localhost:6006, unless the port is already taken.

  • assets/: Contains the project's icons and fonts. The latter are automatically pulled from the editor's selections.
  • build-utils/: The scripts in this folder are used to inject styles and assets from the editor before the project is ran or built.
    • generate-theme.mjs: Entry point for the theme generation logic.
  • components/: The UI components used throughout the project. The component library is organized using Atomic Design principles, and each component contains the following files:
    • ComponentName.stories.tsx file: Used to generate a Storybook entry for the component.
    • ComponentName.tsx: The React component itself.
    • ComponentName.test.tsx (Optional): Behavior interaction tests for the component.
    • index.ts: Re-exports the above files using the Barrell Exports Pattern to simplify imports. Since this folder structure is repeated every time a new component is built, we created a script to scaffold this structure. Usage: ./create_component.sh <component_type> <component_name>
  • config/: Contains the editor's schema. See here for more information on how to customize it.
  • hooks/: Contains the React Hooks used throughout the application.
    • useClassNames: Simplifies toggling class names conditionally.
    • useCurrencySubscription: Utility for automatically refetching product prices on currency change.
    • useDraggableScroll: DOM manipulation hook used within the HorizontalScroller UI component.
    • useLiveEditorQuizNavigation: Used to keep the focus of Horizon and the Editor in sync while editing the Quiz.
    • useLiveEditorQuizResultsNavigation: Used to keep the focus of Horizon and the Editor in sync while editing the Quiz Results page.
    • useLiveEditorQuizUpdates: Used to keep the content of Horizon and the Editor in sync while editing the Quiz.
    • useLiveEditorQuizResultsUpdates: Used to keep the content of Horizon and the Editor in sync while editing the Quiz Results page.
    • useLiveEditorUpdates: Used to keep the content of the dynamic pages in sync between Horizon and the Editor.
    • usePageSections: Transforms the page sections coming from the editor into a list of components.
    • useProductPrice: Formats the products prices in the active locale and currency.
    • useProductSearch: Handles the product search logic.
    • useProductSelection: Handles the state for the user's product selections and returns the active variation data.
    • useProductStock: Returns the stock status and max quantity allowed for the active variation.
    • useQuiz: Handles the quiz steps selection logic.
    • useQuizResults: Fetches and transforms the product data to be shown based on the quiz selections.
  • lib/: Provides quick access to commonly used resources.
    • editor/: Editor-specific utils.
    • globals/: Shared enums, constants, sizes, stylings, etc.
    • graphql/: Contains the GraphQL client, queries and generated sdk.
    • rest/: Rest API requests.
    • shop/: Product specific functions.
    • utils/: Small utility functions divided by category.
  • mock/: Mocked data used for Storybook stories and tests.
  • page_layouts/: Page layout templates as a stand-in for the lack of layout support in Next 12. We were able to create easily reusable layouts that fetch data server-side (for better SEO and responsiveness and to prevent layout shifts) by wrapping the pages with these templates, which are used as Higher Order Components, and by wrapping the getServerSideProps and getStaticSideProps fetching functions with the decorators located within lib/utils/fetch_decorators.ts.
  • pages/: The pages that constitute Horizon.
    • account/: The customer portal.
    • blog/: The blog pages.
    • categories/: The Category Details pages.
    • products/: The Product Details pages.
    • quiz/: The template for each dynamically generated quiz defined within the editor.
    • [[...slug]]: Maps to the dynamic pages defined in the "Pages" section of the editor.
  • stores/: Zustand stores for global accessible state.
    • cart: Stores the cart data.
    • currency: Keeps track of the active currency and its formatting.
    • locale: Keeps track of the stores' locales and the active one.
    • notification: Stores the pop-up notifications shown on specific actions or errors.
    • quiz: Stores the data for the quizzes and their answers.
    • settings: Contains the data for store settings such as colors and typography. These are mapped to environment variables and used throughout the theme.
  • styles/: Contains the auto-generated theme and font stylings, plus some one-off style overrides or utility classes. Most stylings are defined inline through TailwindCSS utility classes.
  • types/: TypeScript type definitions, organized by category.
  • codegen.yml: Contains the configuration options for the graphql-codegen used for end-to-end type safety.
  • create_component: Scaffolds the structure for a new component.
  • lint-staged.config.js: Contains the configuration for lint-staged, which runs before every commit. See the pre-commits section below for more details.

The language settings represent a set of variables that can be translated into multiple languages in the editor and used throughout Horizon.

The config/editor.json file has a lang key which holds all the text variables used throughout the app. If you want to add a new setting follow these steps:

Add the object containing the configuration of the language setting inside lang under the namespace you want (ex: products)

language customization
{
  "lang": {
    //...
    {
      "id": "lang.products.premium_member_label",
      "label": "Premium member label",
      "default": "Premium",
      "type": "short_text",
      "localized": true
    }
  }
}

Add a default value in the config/defaults.json for the new setting (under the same path as the id)

lang config
{
  "lang": {
    //...
    "products": {
      "premium_member_label": "Premium"
    }
  }
}

Push the new settings to the store by running the sync npm script. This will add the new language setting to the editor's schema.

npm run sync
npm run sync

Use the new language setting in the code with the i18n hook

i18n
const i18n = useI18n(); 
const premiumLabel = i18n('products.premium_member_label');

Horizon automatically enters Next.js' preview mode when the NEXT_PUBLIC_SWELL_EDITOR environment variable is set to true. This is done through the preview.ts API route, which sets the cookies necessary to alert Next.js that pages should be rendered on-demand instead of statically. This is important when running alongside the editor so that the changes made within it are immediately reflected in the storefront without requiring a new build.

The calls to the preview.ts API route are done through a useEffect in app.tsx.

Before every commit, the following checks are made:

  • That there are no TypeScript errors.
  • That the ESLint rules are satisfied.
  • That the correct Prettier formatting is used.

This process will automatically attempt to fix the formatting and some of the eslint errors and warnings.

ESLint and TypeScript errors will prevent commits from being completed. See lint-staged.config.js and eslintrc.json for more information on the checks that are made.

Deploying Horizon on Vercel is simple and can be accomplished in only a handful of steps. Firstly ensure you have a Vercel account, otherwise you can signup for one here.

  1. From the Dashboard, select Add new > project.
  2. Connect with the Github account you want to use (or another provider where your repo containing Horizon is located).
  3. Select the repo that contains Horizon from the list and select import.
  4. Under Build and Output Settings, ensure you have the following settings:
    1. Build Command: npm run build
    2. Output Directory: Next.js default
    3. Install Command: npm install
    4. Development Command: npm run dev
  5. Add the same environment variables described above to the Environment Variables section.
  6. Select Deploy to deploy Horizon

You should now have Horizon deployed on Vercel and can add the URL to the dashboard by following the Storefronts > All storefronts > Actions > edit settings > route. Once you've added the URL to the Storefront URL field, select Save changes.