Best Practices

🚧

Important note

Make sure to read the accessibility best practices as well.

localStorage

πŸ“˜

Wrap localStorage in try/catch to ensure the storefront loads in Safari (when cookies are disabled)

  const defaultValue = 'value'

  const getItem = () => {
    if (typeof window === 'undefined') return

    try {
      return localStorage.getItem('key')
    } catch (e) {
      logger.error('Failed to get item in localStorage', e)
    }
  }

  const item = getItem() || defaultValue

console.log

πŸ“˜

Remove all console.log() before publishing your store to production.

Images

  • Always use ResponsiveImage.
  • Use sizes to help browsers load the image in a proper size.
  • Set the width and height to the image to avoid Cumulative Layout Shift (CLS). Another resource here.
    • Images coming from variables inside Shogun Frontend have width and height sent in the media object. See example below:
import React from 'react'
import ResponsiveImage from 'frontend-ui/ResponsiveImage'

const SomeComponent = ({ image }) => (
  <ResponsiveImage
    src={image.src}
    alt="A black t-shirt with a black Shogun logo on it. The t-shirt is on a red surface."
    sizes="(max-width: 767px) 80vw, 40vw"
    width={image.width}
    height={image.height}
   />
)

export default SomeComponent
  • If the image is within the viewport, set loading="eager".

Server side vs. client side

PWAs renders your store on the server side, hence the name SSR (Server Side Rendering). This technique allows your store to be blazing fast, but, mind your code. If you need to use some code that relies on client side APIs, such as window and/or document, do so inside of an useEffect hook:

import React from 'react'

const MyComponent = () => {
  React.useEffect(() => {
    const someElement = document.querySelector('.some-class')
  })

  return <p>My component.</p>
}

export default MyComponent

Detecting viewport width and height

Prefer a CSS approach to detect the width and height of a page. A JS approach such as the useWindowSize hook will cause undesired results, since your store is rendered on the server side (SSR).

❌ Don't do this:

function Header() {
  const { width } = useWindowSize()
  const isMobile = width < 500

  return isMobile ? <MobileHeader /> : <DesktopHeader />
}

βœ… Do this instead:

import styles from "./styles.module.css"

function Header() {
  return (
    <>
      <MobileHeader className={styles.mobile} />
      <DesktopHeader className={styles.desktop} />
    </>
  );
}
.mobile {
  display: block;
}

.desktop {
  display: none;
}

@media (min-width: 500px) {
  .mobile {
    display: none;
  }

  .desktop {
    display: block;
  }
}

Guard clauses

When accessing object properties, make sure the parent property exists. For example, when mapping through an array of images, check if the array empty before mapping through its item.

import React from 'react'
import ResponsiveImage from 'frontend-ui/ResponsiveImage'

const ProductBox = ({ product }) => {
  const { media } = product || {}
    
  // do not render the component
  // if media array is empty
  if (media.length === 0) return

  return (
    <section className="ProductBox">
      <h1>ProductBox</h1>
      {media.map(img => (
        <div key={img.id}>
          <ResponsiveImage
            src={img.src}
            alt=""
            sizes="(max-width: 767px) 80vw, 40vw"
            width={img.width}
            height={img.height}
           />
        </div>
      ))}
    </section>
  )
}

export default ProductBox

CSS classes

For conditional classes, we recommend the classnames npm package.

Naming conventions

Below are some naming conventions recommendations. It's up to you and your team decide a naming convention, and above all, consistency is the key.

  • We recommend using PascalCase for naming your components and sections.
  • Consider using SUIT CSS for CSS classes
  • Prefer descriptive variables names like productImage instead of img (unless the variable context allows for clarity).
  • Variables names must follow the camelCase convention.

Section content variables

When adding content variables to a Section, make sure to select only the data fields that the Section needs. This will help to ensure that Shogun Frontend pages are loaded and displaying critical content with sub second speed.

Example for Navigation section that will display each collection by the Name attribute and include the slug/url to redirect to the correct collection page.

  • To avoid increase in the store's build and publish process, double check if all the variables selected in the IDE for a section are actually in use within the code.

Fonts

  • Inline external fonts to avoid resource chain.
  • Apply font-display: swap to any external font-face rule.
  • If using Google Fonts, make sure your font url has &display=swap on it.

Buttons

  • A button should be used to trigger an action, for example: submit a form. A link is not a button.
  • Use the <button> tag instead of <div className="button">.

Links

Instead of using a normal a tag, use Shogun Frontend Link.

For external links add:

Videos

Use the Video Component to lazy load videos by setting loading="lazy". If the video is on the viewport, use loading="eager".

Iframes

When possible, lazy load iframes with loading="lazy".

Performance

  • Load third-party scripts integrations from the App component.
  • Preconnect common used hosts
    • https://fonts.googleapis.com if using Google fonts
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />

Reusable Functions/React Hooks

It’s expected that you will have functions or custom React Hooks that needed to be reused in other parts of your codebase.

To accomplish this, we suggest creating a Component for each grouping in a manner that’s clear to its contents such as ProductPageHooks or GlobalUtilFunctions with each function being exported:

import React from 'react'

export const someFunction = () => {
   ...
}

export const someFunction2 = () => {
   ...
}

You can then utilize these functions anywhere in other Components or Sections via imports:

import { someFunction, someFunction2 } from "Components/GlobalUtilFunctions";