<aside> ❗ This guide suggests that most of basic optimization is already done, by following next guides:
How to preload critical chunks?
How to split preload and rendering?
</aside>
Before starting to optimize PDP it is required to take an analysis of the page to determine its critical elements and what should be considered as the high priority element (LCP).
By default, in most cases LCP element on PDP is considered to be one of the main product card image in the viewport.
Locate the product gallery chunk. By default, in the ScandiPWA core, the product gallery can be found in ProductPage
component. In this case, the product-gallery
chunk will need to be preloaded.
export const ProductGallery = lazy(() => import(
/* webpackMode: "lazy", webpackChunkName: "product-gallery" */
'Component/ProductGallery'
));
Add product-gallery
chunk to preload configuration’s cacheGroupWhitelist
<aside> 🧠 Read more about preload configuration here: How to preload critical chunks?
</aside>
const cacheGroupWhitelist = ['cms', 'product', 'category', 'render', 'widget-slider', 'product-gallery'];
Add product-gallery
chunk to chunkValidator
to generate a preload link for it
const chunkValidator = {
// other chunks
'product-gallery': window.actionName.type === 'PRODUCT'
}
To further optimize the LCP time load, We need to load and show the product image and call setIsPriorityLoaded
once the product image is loaded.
Let’s override the ProductGallery.component
's renderImage
method and call setIsPriorityLoaded
accordingly.
renderImage(mediaData, index) {
const {
isZoomEnabled,
handleZoomChange,
disableZoom,
isMobile,
isImageZoomPopupActive,
showLoader
} = this.props;
const { scrollEnabled } = this.state;
if (!isMobile) {
const {
base: { url: baseSrc } = {},
large: { url: largeSrc } = {}
} = mediaData;
const style = isImageZoomPopupActive ? { height: 'auto' } : {};
const src = isImageZoomPopupActive ? largeSrc : baseSrc;
return (
<Image
key={ index }
src={ src }
ratio="custom"
mix={ {
block: 'ProductGallery',
elem: 'SliderImage',
mods: { isPlaceholder: !src }
} }
isPlaceholder={ !src }
style={ style }
showIsLoading={ showLoader }
onImageLoad={ setIsPriorityLoaded }
/>
);
}
return (
<TransformWrapper
key={ index }
onZoomChange={ handleZoomChange }
onWheelStart={ this.onWheelStart }
onWheel={ this.onWheel }
wheel={ { limitsOnWheel: true, disabled: !scrollEnabled } }
// doubleClick={ { mode: 'reset' } }
pan={ {
disabled: !isZoomEnabled,
limitToWrapperBounds: true,
velocity: false
} }
options={ {
limitToBounds: true,
minScale: 1
} }
>
{ ({
scale,
previousScale,
resetTransform,
setTransform
}) => {
if (scale === 1 && previousScale !== 1) {
resetTransform();
}
return (
<ProductGalleryBaseImage
setTransform={ setTransform }
index={ index }
mediaData={ mediaData }
scale={ scale }
previousScale={ previousScale }
disableZoom={ disableZoom }
isZoomEnabled={ isZoomEnabled }
/>
);
} }
</TransformWrapper>
);
}
Even though we have prioritized the product image as it’s a LCP on a page, we are still waiting for the rendering chunks to be loaded to start downloading the slide image. This can be further improved if we will preload the image beforehand in the document.
Let’s add a preload for it index.php
const {
actionName: {
imageUrl,
type
} = {}
} = window;
if (type === 'PRODUCT' && imageUrl && imageUrl !== '') {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'image';
link.href = imageUrl;
document.head.appendChild(link);
}
<aside> 🔗 How to de-prioritize non-critical requests?
</aside>
To minimize the size of the product chunk and reduce all unnecessary code that is not considered to be part of the critical rendering path, it should be deprioritized.
It is required to render only LCP elements (usually images) as soon as possible.
This means, that on Product page, almost everything can be deprioritized!
Lets check how the product chunk looks in bundle analyzer and determine its size before deprioritization:
So at the moment, size of product chunk in Gzip is 68.7 KB
To deprioritize unnecessary components it is required to copy source Product Page component which is located in node_modules/@scandipwa/scandipwa/route/ProductPage
and put into the projects src/route/ProductPage
folder, it can be renamed to ProductPage.source.component.js
file to avoid file duplicates. You would also need to copy project theme Product Page component which is located in node_modules/@scandiweb/<PROJECT-THEME>/src/route/ProductPage
and put into the projects src/route/ProductPage
folder, it can be renamed to ProductPage.theme.component.js
file to avoid file duplicates This is needed to change existing inline imports of components to deprioritize them with lowPriorityLazy
method.
Now, in ProductPage.source.component.js
and ProductPage.theme.component.js
change importing of components from inlining them to lowPriorityLazy
.
For example, component ProductActions
was imported into ProductPage.source.component.js
using inline import:
import ProductActions from 'Component/ProductActions';
This import should be changed with deprioritization and webpack chunk name prefers to be product-misc:
export const ProductActions = lowPriorityLazy(() => import(
/* webpackMode: "lazy", webpackChunkName: "product-misc" */
'Component/ProductActions'
));
Now when a component is lazy loaded with low priority it is required to wrap it with Suspense
component in extended file. Create ProductPage.component.js
file where ProductPage class will be extended from previously copied source component and extending method which is responsible for rendering ProductActions component.
import { Suspense } from 'react';
/**
* Importing ProductPage class and ProductActions component
* from copy of local source file
*/
import {
ProductPage as SourceProductPage,
ProductActions,
} from './ProductPage.source.component';
export class ProductPage extends SourceProductPage {
renderStickyBottomSection() {
const {
...
} = this.props;
...
// Here ProductActions component is wrapped with Suspense
return (
...
<Suspense fallback={ null } >
<ProductActions
...
/>
</Suspense>
...
);
}
}
export default ProductPage;
This should be done for all components that are not considered to be in critical rendering path.
In result there should be a blank page, which is expected, since all of the components in ProductPage component are loaded with low priority.
After deprioritizing everything on a page, bundle analyzer shows less size code for product chunk.