Skip to content

Commit

Permalink
feat/improve page speed (#18)
Browse files Browse the repository at this point in the history
* Adds useOnScreen hook to monitor element visibility using Intersection Observer for optimized rendering.
* Extended Next.js SSG revalidate time for reduced server load and improved cache utilization.
* adjust image quality on mobile and addressed accessibility issues
* chore: organized imports in layout.tsx
  • Loading branch information
dalindev committed Aug 30, 2023
1 parent b178102 commit 11c84dc
Show file tree
Hide file tree
Showing 24 changed files with 636 additions and 485 deletions.
2 changes: 2 additions & 0 deletions composable-ui/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ module.exports = () => {
],
images: {
domains: ['loremflickr.com'],
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 60 * 60 * 24 * 30,
},
i18n: {
locales: ['en-US'],
Expand Down
2 changes: 1 addition & 1 deletion composable-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"dependencies": {
"@chakra-ui/icons": "^2.0.17",
"@chakra-ui/react": "^2.5.5",
"@chakra-ui/react": "^2.8.0",
"@chakra-ui/theme-tools": "^2.0.16",
"@composable/cms-generic": "workspace:*",
"@composable/commerce-generic": "workspace:*",
Expand Down
20 changes: 20 additions & 0 deletions composable-ui/src/components/__mocks__/intersection-observer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const mockedIntersectionObserver = class {
root: null
rootMargin: '0px'
thresholds: [0]

constructor(callback: IntersectionObserverCallback) {
this.root = null
this.rootMargin = '0px'
this.thresholds = [0]

callback([{ isIntersecting: true } as IntersectionObserverEntry], this)
}

observe() {}
disconnect() {}
unobserve() {}
takeRecords(): IntersectionObserverEntry[] {
return []
}
}
11 changes: 6 additions & 5 deletions composable-ui/src/components/__tests__/home-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React from 'react'
import { render, screen } from 'utils/tests'
import { api } from 'utils/api'
import { HomePage } from '../home-page'
import { mockedIntersectionObserver } from 'components/__mocks__/intersection-observer'
import { LAZY_LOAD_BATCH_SIZE } from 'utils/constants'

global.IntersectionObserver = mockedIntersectionObserver
jest.mock('utils/api')
jest.mock('../cms', () => ({
BannerFull: jest
Expand Down Expand Up @@ -40,16 +43,14 @@ describe('HomePage', () => {
)
})

it('renders cms each item properly', async () => {
it('renders first batch of cms items', async () => {
const items = await screen.findAllByRole('cms-page-item')

const { data } = api.cms.getPage.useQuery({ slug: 'home' })
if (!data) return
const pageItems = data.items

for (const item of pageItems) {
const index = pageItems.indexOf(item)
expect(items[index]).toHaveAttribute('data-testid', item.__typename)
for (let i = 0; i < LAZY_LOAD_BATCH_SIZE; i++) {
expect(items[i]).toHaveAttribute('data-testid', data.items[i].__typename)
}
})
})
4 changes: 3 additions & 1 deletion composable-ui/src/components/composable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const Composable = ({
<>
<GoogleTagManager googleTagManagerId={googleTagManagerId} />
<ChakraProvider theme={theme}>{children}</ChakraProvider>
<ReactQueryDevtools initialIsOpen={false} />
{process.env.NODE_ENV === 'development' && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</>
</IntlProvider>
</ComposableProvider>
Expand Down
37 changes: 27 additions & 10 deletions composable-ui/src/components/home-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
} from './cms'
import { UiContainer } from '@composable/ui/src/components/ui-container'
import { PageItem } from '@composable/types'
import { useOnScreen } from 'hooks'
import React, { useState, useEffect } from 'react'
import { LAZY_LOAD_BATCH_SIZE } from 'utils/constants'

const renderItem = (item: PageItem) => {
switch (item?.__typename) {
Expand Down Expand Up @@ -39,24 +42,38 @@ export const HomePage = () => {
refetchOnMount: false,
}
)
const [visibleCount, setVisibleCount] = useState(LAZY_LOAD_BATCH_SIZE)
const [loaderRef, isLoaderVisible] = useOnScreen<HTMLDivElement>({
rootMargin: '200px',
})

useEffect(() => {
if (isLoaderVisible) {
setVisibleCount((prevCount) => prevCount + LAZY_LOAD_BATCH_SIZE)
}
}, [isLoaderVisible, data])
return (
<Box>
<NextSeo
title="Composable UI Open Source Storefront"
description="Welcome to Composable UI! Create impactful, online storefronts with a foundational React and Next.js design system and UI library for modern composable commerce websites."
/>
<Container maxW="container.xl">
{data?.items?.map((item) => (
<UiContainer
key={item?.id}
size={item?.containerSize}
marginBottom={item?.containerMarginBottom}
marginTop={item?.containerMarginTop}
>
{renderItem(item)}
</UiContainer>
))}
{data?.items?.slice(0, visibleCount).map((item) => {
return (
<UiContainer
key={item?.id}
size={item?.containerSize}
marginBottom={item?.containerMarginBottom}
marginTop={item?.containerMarginTop}
>
{renderItem(item)}
</UiContainer>
)
})}
{visibleCount < (data?.items?.length || 0) && (
<div ref={loaderRef}></div>
)}
</Container>
</Box>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const LinkStack = ({ item, level = 0 }: LinkStackProps) => {
href={href}
padding={0}
textStyle={'Desktop/Body-S'}
prefetch={false}
>
{label}
</Link>
Expand Down
48 changes: 33 additions & 15 deletions composable-ui/src/components/layout/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React, { ReactElement } from 'react'
import { ReactElement } from 'react'
import dynamic from 'next/dynamic'
import NextLink from 'next/link'
import { useRouter } from 'next/router'
import { DefaultSeo } from 'next-seo'
import { Box, Flex, Link, Text } from '@chakra-ui/react'

import { APP_CONFIG } from 'utils/constants'
import { cmsFooterLinks } from './_data'
import { getSiteUrl } from 'utils/get-site-url'
import { useComposable } from 'hooks'
import { Header } from './header'
import { Footer } from './footer'
import { useRouter } from 'next/router'
import { cmsFooterLinks } from './_data'
import { Logo } from '../logo'
import NextLink from 'next/link'
import { useComposable } from 'hooks'
import { useOnScreen } from '../../hooks'

const DynamicCartDrawer = dynamic(() =>
import('components/cart').then((_module) => _module.CartDrawer)
Expand All @@ -23,6 +24,11 @@ const DynamicMenuDrawer = dynamic(() =>
const DynamicAccountDrawer = dynamic(() =>
import('../account/account-drawer').then((_module) => _module.AccountDrawer)
)

const DynamicFooter = dynamic(() =>
import('./footer').then((_module) => _module.Footer)
)

interface Props {
children?: ReactElement | ReactElement[]
}
Expand All @@ -31,6 +37,9 @@ export const Layout = ({ children }: Props) => {
const { locale, cartDrawer, menuDrawer, accountDrawer } = useComposable()
const router = useRouter()
const isCheckout = router.pathname === '/checkout'
const [footerRef, footerVisible] = useOnScreen<HTMLDivElement>({
rootMargin: '300px',
})

const SeoConfig = (
<DefaultSeo
Expand Down Expand Up @@ -71,14 +80,18 @@ export const Layout = ({ children }: Props) => {
{children}
</Box>

<Footer
brandLogo={<Logo h={6} />}
copyrightText={APP_CONFIG.COPYRIGHT}
homeUrl={'/'}
tagline={APP_CONFIG.TAG_LINE}
message={<FooterMessage />}
parentMenuItems={cmsFooterLinks}
/>
<Box ref={footerRef}>
{footerVisible && (
<DynamicFooter
brandLogo={<Logo h={6} />}
copyrightText={APP_CONFIG.COPYRIGHT}
homeUrl={'/'}
tagline={APP_CONFIG.TAG_LINE}
message={<FooterMessage />}
parentMenuItems={cmsFooterLinks}
/>
)}
</Box>
</Flex>

{cartDrawer.isOpen && <DynamicCartDrawer />}
Expand All @@ -93,7 +106,12 @@ const FooterMessage = () => {
<Box pt={8}>
<Text textStyle={'Desktop/Body-XL'}>
{APP_CONFIG.FOOTER_MESSAGE + ' '}
<Link as={NextLink} href={APP_CONFIG.URL} textDecor={'underline'}>
<Link
as={NextLink}
href={APP_CONFIG.URL}
textDecor={'underline'}
prefetch={false}
>
{APP_CONFIG.URL_TEXT}
</Link>
</Text>
Expand Down
9 changes: 7 additions & 2 deletions composable-ui/src/components/layout/login-action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const LoginAction = (props: { rootProps?: BoxProps }) => {
signIn: intl.formatMessage({ id: 'action.signIn' }),
myAccount: intl.formatMessage({ id: 'account.dashboard.title' }),
}
const loggedIn = session && session.loggedIn

return (
<Box display={'flex'} {...props.rootProps}>
Expand All @@ -28,7 +29,11 @@ export const LoginAction = (props: { rootProps?: BoxProps }) => {
textDecoration={'underline'}
textUnderlineOffset={'5px'}
aria-label={
accountDrawer.isOpen ? message.closeMyAccount : message.openMyAccount
loggedIn
? accountDrawer.isOpen
? message.closeMyAccount
: message.openMyAccount
: message.signIn
}
onClick={() => {
menuDrawer.onClose()
Expand All @@ -55,7 +60,7 @@ export const LoginAction = (props: { rootProps?: BoxProps }) => {
</Box>

<Text textStyle={'Desktop/Body-S'} fontWeight={'700'}>
{session && session.loggedIn ? message.myAccount : message.signIn}
{loggedIn ? message.myAccount : message.signIn}
</Text>
</Button>
</Box>
Expand Down
1 change: 1 addition & 0 deletions composable-ui/src/components/menu/menu-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const MenuDrawer = () => {
href={`/category/${item.slug}`}
key={item.slug}
onClick={onClose}
prefetch={false}
>
<Text
maxW="md"
Expand Down
1 change: 1 addition & 0 deletions composable-ui/src/components/menu/menu-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const MenuItem = ({
fontWeight: '700 !important',
borderColor: 'primary',
}}
prefetch={false}
{...rootProps}
>
{children ? (
Expand Down
1 change: 1 addition & 0 deletions composable-ui/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './use-cart'
export * from './use-checkout'
export * from './use-composable'
export * from './use-on-screen'
export * from './use-toast'
33 changes: 33 additions & 0 deletions composable-ui/src/hooks/use-on-screen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useState, useEffect, useRef } from 'react'

type Options = {
root?: Element | null
rootMargin?: string
threshold?: number | number[]
}

export const useOnScreen = <T extends HTMLElement>(
options?: Options
): [React.RefObject<T>, boolean] => {
const ref = useRef<T>(null)
const [isVisible, setIsVisible] = useState(false)

useEffect(() => {
const currentRef = ref.current
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting)
}, options)

if (currentRef) {
observer.observe(currentRef)
}

return () => {
if (currentRef) {
observer.unobserve(currentRef)
}
}
}, [options])

return [ref, isVisible]
}
2 changes: 1 addition & 1 deletion composable-ui/src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { randomUUID } from 'crypto'
import NextAuth, { type NextAuthOptions } from 'next-auth'
import NextAuth from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
import GoogleProvider from 'next-auth/providers/google'
import { NextApiRequest, NextApiResponse } from 'next'
Expand Down
4 changes: 2 additions & 2 deletions composable-ui/src/pages/category/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const getStaticProps: GetStaticProps<CategoryPageProps> = async (
algoliaKeysSet: !algoliaKeysMissing,
query: slug,
},
revalidate: 10,
revalidate: 60,
}
}

Expand All @@ -64,7 +64,7 @@ export const getStaticProps: GetStaticProps<CategoryPageProps> = async (
query: slug,
serverState,
},
revalidate: 10,
revalidate: 60,
}
}

Expand Down
2 changes: 1 addition & 1 deletion composable-ui/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
// This is to prevent flashing of content - if the page was refetched every
// time the user visited it, they would likely see a brief flash of an older
// version of the page before the updated content is loaded.
revalidate: 1,
revalidate: 60,
}
}

Expand Down
2 changes: 1 addition & 1 deletion composable-ui/src/pages/product/[slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
props: {
trpcState: ssg.dehydrate(),
},
revalidate: 1,
revalidate: 60,
}
}

Expand Down
1 change: 1 addition & 0 deletions composable-ui/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ export const ALGOLIA_SORTING_OPTIONS = [
export const GOOGLE_TAG_MANAGER_ID =
process.env.NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID
export const PASSWORD_MIN_LENGTH = 8
export const LAZY_LOAD_BATCH_SIZE = 3
4 changes: 2 additions & 2 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
"devDependencies": {
"@chakra-ui/anatomy": "^2.1.1",
"@chakra-ui/cli": "^2.3.0",
"@chakra-ui/react": "^2.5.5",
"@chakra-ui/react": "^2.8.0",
"@chakra-ui/styled-system": "^2.5.1",
"eslint-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"typescript": "^4.5.5"
},
"peerDependencies": {
"@chakra-ui/icons": "^2.0.4",
"@chakra-ui/react": "^2.5.5",
"@chakra-ui/react": "^2.8.0",
"@chakra-ui/theme-tools": "^2.0.5",
"@emotion/react": "^11.9.3",
"@emotion/styled": "^11.9.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/cms/banner-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const BannerImage = ({
style={{ objectFit: 'cover' }}
loading={isLazy ? 'lazy' : 'eager'}
priority={!isLazy}
sizes={'calc(100vw - 64px)'}
sizes={'50vw'}
/>
{overlayBackground && (
<Box
Expand Down
Loading

2 comments on commit 11c84dc

@vercel
Copy link

@vercel vercel bot commented on 11c84dc Aug 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines Statements Branches Functions
Coverage: 44%
43.54% (297/682) 23.35% (46/197) 23.23% (33/142)

Please sign in to comment.