Catwalk Performance

When creating a top performing site, performance optimizations are crucial. Below are some ideas and tools so you can improve the performance of your Projects. While we try to implement as much as possible in the framework itself, there are some decisions only you can make.

In this article:

Deferred Tastics

You can reduce the overall build size of your JavaScript as some Tastics can be deferred since they're not relevant to Search engines.

To defer the loading of a Tastic you can use our asyncComponent helper inside the tastic/tastics.js.

To do this, find the usual import of a Tastic which looks like this:

import CheckoutTastic from './checkout/tastic.jsx'

And replace with the following lines:

import asyncComponent from 'frontastic-catwalk/src/js/component/asyncComponent'
const CheckoutTastic = asyncComponent({
     import: () => {
	return import('./checkout/tastic.jsx')
     },
     height: { desktop: 690, tablet: 530, mobile: 542 }, 
})

Everything will stay the same. The height will be used to reserve some space for the Tastic during asynchronous rendering to reduce the screen flickering. Tastics wrapped into asyncComponent will be put into their own chunks by the default webpack configuration.

To analyze the build size and find out which Tastics have the most effect on the build size you can analyze the build size yourself:

1
Go into the Project folder you want to analyze
2
Run ant package
3
Inspect build/bundleSize.html (HTML report) or build/bundleStats.json (webpack bundle statistics)
With this, it should be possible for you to optimize imports, libraries and decide which Tastics should be deferred.

Stream Data Optimizations

Data fetched from backend systems as configured by the Frontend Managers is crucial to Frontastic as we don't store any data ourselves but load all data from third party systems. We already implement some optimizations ourselves:

  • Only load streams assigned to Tastics
  • Load all Streams in parallel
  • Optionally cache Stream data
  • Limit fetched item count

Still, the data fetched has to be transmitted to the Server Side Rendering and to the Client, so it can make sense to optimize the data. This can already be done using a API decorator but we have an additional concept which knows more about the context of the Stream, the StreamOptimizer.

Stream Optimizer

A Stream Optimizer will receive the data of all Streams and some context information:

  • The current Node and Page
  • The current context (Customer, Project, Session, …)
  • The Tastics currently using this Stream
  • The Stream itself and its parameters

Based on this information you can programmatically decide if you want to remove data from the Stream.

It's very common, for example, that everything which refers to product lists needs a lot less data about a Product than a Product Detail Page needs. While Frontastic by default also provides all product lists with all the product data, we can use a Stream Optimizer to limit the transmitted data. For this we must do two things:

  1. Create the Optimizer
  2. Register the Optimizer
1
Create The Optimizer
The Optimizer is a PHP class that will receive all data from all Streams and can return updated data based on this. In this case, we remove a lot of information from the products in a product-list stream because those aren't necessary for any of the product lists in our project:
<?php
namespace My\StreamOptimizer;
use Frontastic\Catwalk\FrontendBundle\Domain\StreamOptimizer;
use Frontastic\Catwalk\FrontendBundle\Domain\StreamContext;
use Frontastic\Catwalk\FrontendBundle\Domain\Stream;
use Frontastic\Common\ProductApiBundle\Domain\Product;
use Frontastic\Common\ProductApiBundle\Domain\Variant;
class MinimalProduct implements StreamOptimizer
{
    /**
     * @return mixed
     */
    public function optimizeStreamData(
	Stream $stream, 
	StreamContext $streamContext, 
	$data
    ) {
        //Ignore all streams but product lists
        if ($stream->type !== 'product-list') {
            return $data;
        }
        foreach ($data->items as $index => $product) {
            $data->items[$index] = new Product([
                'productId' => $product->productId,
                'slug' => $product->slug,
                'name' => $product->name,
                'variants' => [
                    new Variant([
                        'sku' => $product->variants[0]->sku,
                        'price' => $product->variants[0]->price,
                        'images' => $product->variants[0]->images,
                        'attributes' => array_intersect_key(
                            $product->variants[0]->attributes,
                            //Product attributes we want to keep 
                            array_flip(['designer', 'badges'])
                        ),
                    ])
                ]
            ]);
        }
        return $data;
    }
}

In this Stream Optimizer, we create new Products with a very small data subset from the existing products. While this seems irrelevant, stripping data will help a lot because it reduces time spent in encoding and decoding data and the amount of data transmitted over the network.

Based on the Tastics using the current Stream, you can decide which attributes you want to keep. Take a look at the  StreamContext for all this context information.

2
Register the Optimizer
The next thing you need to do is register the Optimizer inside the dependency injection container using the service tag frontend.streamOptimizer.
<service id="My\StreamOptimizer\MinimalProduct">
     <tag name="frontend.streamOptimizer" />
</service>

Now, all Product Lists will be stripped down on the Server. You should see less data in the frontend and have increased performance overall.

Default Implementation

Since the Optimizer implemented above is a very common case, we provide you with a simple default implementation that you can use by adding the following to your dependency injection container configuration:

<service id="Frontastic\Catwalk\FrontendBundle\Domain\StreamOptimizer\MinimalProduct">
     <argument type="collection">
	<argument>designer</argument>
	<argument>badges</argument>
     </argument>

     <tag name="frontend.streamOptimizer" />
</service>

Tastify – Tastic Rendering Optimizations 

Constantly Changing Data-Prop

The data prop that Tastics receive was recreated too often in the past.

It's now being memorized in the TasticWrapper-Component. Because of this, for a PureComponent no unnecessary re-render would be caused for data prop.

In addition to data, Tastics also receive a rawData prop, that's still changing very often. rawData is only required in edge cases. Some legacy Tastics might still use it. If your Tastic currently uses rawData, please check if you can migrate it to use data instead. If that's not possible, please let us know what you are missing to work without rawData.

Remove Unnecessary Props from Tastics

Tastify removes rawData, node, and page props from the Tastic per default. You can of course opt-in to receive them. This makes sure that your Tastic only re-renders when the data prop changes.

Adapting a Tastic

Instead of connecting a Tastic with the Redux-store using the connect helper, you can use the Tastify Higher Order Component and opt-in to additional properties you want to receive:

export default tastify({
     connect: {
	context: true,
	node: true,
	cart: true,
	wishlist: true,
     }
})(NavigationTastic)

You'll always receive the Tastic configuration data, which includes the Stream data.

Currently, the following types are supported:

  • connect.cart: Whether to pass information about the current cart
  • connect.context: Whether to pass the Frontastic context object
  • connect.deviceType: Whether to pass the deviceType
  • connect.isServerSideRendering: Whether we should pass a flag isServerSideRendering
  • connect.node: Whether to pass information about the current Node
  • connect.page: Whether to pass information about the current Page
  • connect.rawData: Whether to pass "rawData" but it shouldn't be needed anymore
  • connect.tastic: Whether to pass the Schema of the current Tastic but this is rarely needed
  • connect.wishlist: Whether to pass information about the current wishlist
Checking if Tastic is Currently Rendered Inside Server Side Rendering

With tastify we also added the ability to check inside a Tastic whether we are running inside a Server Side Rendering process.

In order to get that information you need to enable the connect configuration for isServerSideRendering:

export default tastify({
     connect: {
	isServerSideRendering: true,
     }
})(YourTastic)<br>
	

Then you could check for the isServerSideRendering prop inside your Tastic to know whether this call comes from the browser or our SSR process.

Summary

The re-renders for components like this go down significantly. The less data you depend on, the less re-renders will happen in your components.


‹ Back to Article List

Next Article ›

Debugging with PHP Storm

Still need help? Contact Us Contact Us