<aside> 🧠 You should already be familiar with how the data is preloaded in the document. Read more about it here:

How to preload critical data?

</aside>

1. Preload Slider data in the document

  1. add a new entry to actionName in index.php

    window.actionName = {
        slider: <?= json_encode($this->getSlider()); ?> || {}
    };
    
  2. extend the Page.php by preferencing the ScandiPWA\\Router\\View\\Result\\Page class to add new property slider, where we will store our preloaded slider data.

    <aside> 🧠 if you are unfamiliar with a Magento 2 preference, follow the following instructions

    How to preference a class?

    </aside>

    use ScandiPWA\\Customization\\View\\Result\\Page as SourcePage;
    
    class Page extends SourcePage
    {
    	/**
       * @var array|null
       */
      protected $slider;
    
    	public function __construct(
    		// __construct arguments here
      ) {
    			// call parent __construct here
    
          $this->slider = null;
      }
    	
    	/**
    	 * Set preloaded slider
    	 * @param string
    	 * @return \\Magento\\Framework\\View\\Result\\Page
    	 */
    	public function setSlider($slider)
    	{
    	    if ($this->slider === null) {
    	        $this->slider = $slider;
    	        return $this;
    	    }
    	
    	    return null;
    	}
    	
    	/**
       * Retrieve preloaded slider
       *
       * @return  string
       */
    	public function getSlider()
    	{
    	    return $this->slider;
    	}
    
  3. set the preloaded slider data on an action in RouterPlugin in this example we will set the slider data for a homepage, but it can be set on any page type ( e.g. CMS page ).

    protected function setResponseHomePage(ActionInterface $action, $homePageIdentifier)
    {
        try {
    				$page = $this->pageProvider->getDataByPageIdentifier($homePageIdentifier, (int)$this->storeId);
    
    				$action->setType(self::PAGE_TYPE_CMS_PAGE);
            $action->setId($page['page_id'] ?? '');
            $action->setPage($page);
    				$action->setSlider($this->getSliderInformation($page['content'] ?? ''));
        } catch (\\Throwable $th) {
            $this->setNotFound($action);
        }
    }
    
  4. create a getSliderInformation method, which will return the slider data

    use Magento\\Framework\\Filter\\Template\\Tokenizer\\Parameter as TokenizerParameter;
    use ScandiPWA\\SliderGraphQl\\Model\\Resolver\\Slider as SliderResolver;
    
    class RouterPlugin
    {
      protected TokenizerParameter $tokenizerParameter;
    
      protected SliderResolver $sliderResolver;
    
    	public function __construct(
    		TokenizerParameter           $tokenizerParameter,
        SliderResolver               $sliderResolver
    	)
    	{
    		$this->tokenizerParameter = $tokenizerParameter;
        $this->sliderResolver = $sliderResolver;
    	}
    
    	/**
    	 * @param string $content
    	 *
    	 * This function gets first widget from page content
    	 * In case if it is slider, then it gets slider it.
    	 */
    	protected function getSliderInformation($content)
    	{
    	    preg_match('/{{(.*?)}}/m', $content, $match);
    	
    	    $this->tokenizerParameter->setString($match[0] ?? '');
    	    $params = $this->tokenizerParameter->tokenize();
    	
    	    if (isset($params['slider_id'])) {
    	        return $this->sliderResolver->getSlider($params['slider_id']);
    	    }
    	}
    }
    
  5. Preload the slider chunk

    1. locate the slider chunk. by default, in the ScandiPWA core, the slider widget can be found in WidgetFactory component. In this case, the widget-slider chunk will need to be preloaded.

      export const HomeSlider = lazy(() => import(
          /* webpackMode: "lazy", webpackChunkName: "widget-slider" */
          'Component/SliderWidget'
      ));
      
    2. add widget-slider chunk to preload configuration’s cacheGroupWhitelist

      <aside> 🧠 Read more about preload configuration here:

      How to preload critical chunks?

      </aside>

      const cacheGroupWhitelist = ['widget-slider', 'render', 'cms', 'product', 'category'];
      
    3. add widget-slider chunk to chunkValidator to generate a preload link for it

      const chunkValidator = {
      	// other chunks
      	'widget-slider': window.actionName.type === 'CMS_PAGE' && Object.keys(window?.actionName?.slider || {}).length,
      }
      
  6. Prevent unnecessary request, if the slider data has already been preloaded

    <aside> 🧠 if you are unfamiliar with ScandiPWA components and override mechanism read more about it here:

    Override Mechanism

    </aside>

    extend the SliderWidget.container ’s requestSlider method and add a condition that will check whether the requested slider id matches the one that has been preloaded in the document:

    requestSlider() {
        const { sliderId } = this.props;
        const {
            actionName: {
                slider: {
                    slider_id: preloadedSliderId
                } = {},
                slider: preloadedSlider = {}
            }
        } = window;
    
        if (sliderId === Number(preloadedSliderId)) {
            this.setState({ slider: preloadedSlider });
    
            return;
        }
    
    		// slider data hasn't been preloaded, make a request for it
        return super.requestSlider();
    }
    

    <aside> 🎉 We got the slider data early right in the HTML document!

    </aside>

2. Optimize the LCP media once the slider chunk has been preloaded

at this point the rendering chain should look something similar:

Untitled

to further optimize the LCP time load, we can deprioritize the slides that aren’t visible. meaning only load the active first slide. to visualize, this is how the render chain should look like:

Untitled

  1. Let’s override the SliderWidget.component's renderSlide method and check whether the slide is currently active or not and wrap it with AfterPriority component accordingly

    <aside> 🧠 if you are not familiar with AfterPriority component, read more about it here:

    How to de-prioritize non-critical requests?

    </aside>

    renderSlideImage(slide, i) {
        const {
            isPlaceholder,
            title: block,
            slide_text
        } = slide;
    
        return (
            <figure
              block="SliderWidget"
              elem="Figure"
              key={ i }
              aria-hidden={ i !== this.state.activeImage }
            >
                <Image
                  mix={ { block: 'SliderWidget', elem: 'FigureImage' } }
                  ratio="custom"
                  src={ this.getSlideImage(slide) }
                  alt={ block }
                  isPlaceholder={ isPlaceholder }
                  onLoad={ setIsPriorityLoadedFlag }
                />
                <figcaption
                  block="SliderWidget"
                  elem="Figcaption"
                  mix={ { block } }
                >
                    <Html content={ slide_text || '' } />
                </figcaption>
            </figure>
        );
    }
    
    renderSlide(slide, i) {
        const { activeImage } = this.state;
        const { isPriorityLoaded } = window;
    
        if (activeImage !== i && !isPriorityLoaded) {
            return (
                <AfterPriority fallback={ <div /> }>
                    { this.renderSlideImage(slide, i) }
                </AfterPriority>
            );
        }
    
        return this.renderSlideImage(slide, i);
    }
    

    <aside> 🎉 We got the first slide loaded with priority and further improved the LCP load time

    </aside>

3. Preload the first slide of a widget right into the document

even though we have prioritized the first slide 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. to visualize it, this is how the rendering path can look like:

Untitled

Let’s add a preload for it index.php

const appendPreloadLink = (as, href) => {
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = as;
    link.href = href;
    document.head.appendChild(link);
}
const {
    actionName: {
        slider = {},
        slider: { slides = {} } = {},
    } = {}
} = window;

// Preload for slider first image
if (Object.keys(slider).length) {
    const [{ desktop_image, mobile_image }] = slides;

    const imageUrl = window.matchMedia('(max-width: 810px)').matches
        && window.matchMedia('screen').matches
        ? (mobile_image || desktop_image)
        : (desktop_image || mobile_image);

    appendPreloadLink('image', `/${imageUrl}`);
}

<aside> 🎉 We got the LCP media from the slider widget significantly faster

</aside>