๐ง 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
andheight
to the image to avoid Cumulative Layout Shift (CLS). Another resource here.Images coming from variables inside Shogun Frontend have
width
andheight
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:
index.js
function Header() {
const { width } = useWindowSize()
const isMobile = width < 500
return isMobile ? <MobileHeader /> : <DesktopHeader />
}
โ Do this instead:
index.js
import styles from "./styles.module.css"
function Header() {
return (
<>
<MobileHeader className={styles.mobile} />
<DesktopHeader className={styles.desktop} />
</>
);
}
styles.module.css
.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 ofimg
(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 externalfont-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:
target="_blank"
to open in a new tab.rel="noreferrer noopener"
to prevent security vulnerabilities.
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.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";