magento2: Checkout page very large and quite slow.

Using a single store on 2.6 in production mode. No combined JS/css, with setup:di:compile, varnish, and SSL for checkout.

Normal pages work quickly through varnish as expected in 2s/100ms uncached/cached and around 12k each. Customer pages like past orders come in without varnish as expected in 1.5-2.5s at around 40k. Going to the checkout with a single item in my cart take 4.5s and weights in at 800k and seams to go up in size and response time.

Note that these times are for the roundtrip for the html file, not any resources or images.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 1
  • Comments: 49 (17 by maintainers)

Commits related to this issue

Most upvoted comments

I am on 2.1.6 and face same issues. The module can be found here: https://marketplace.magento.com/thlassche-performancetweaks.html @tomlever

Yes. I would say this is a critical issue and makes us look very bad to our customers. The site, products, and custom loggin pages are all quite fast @.5ms for cached requests and 1s for uncached requests. But anyone getting to the checkout page is in for a nice 10s delay and gets very irritated. We have even disabled some unused of the shipping modules per advice I found but that had a very minimal impact on the performance.

To clarify the checkout page is 50 times larger then any other page on the site and its loading 5 times as much javascript code. Or with bundling turned off its loading 50-90 separate scripts per page load.

Also the way that the varnish/caching works the /checkout and its resources don’t benefit at all form these systems. This means that the checkout page spends 1.85s processing in PHP with all the setup:di:compile complications, all caches enabled, and opcach set up. It then has to spend a further 1.5s downloading the large file. Then it gets to loading wither 90+ scripts or the 3.5MB bundles file either way taking another 6s.

@SchumacherFM @ishakhsuvarov @pynej I fixed this by adding a layout processor plugin. It checks whether the default payment methods of Magento are enabled or not, and if they are disabled it removes them from the JS layout. Dramatically reducing the page load of the checkout page

di.xml:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
        <plugin name="checkoutLayoutProcessor" type="\<vendor>\<moduleName>\Plugin\CheckoutLayoutProcessor" />
    </type>
</config>

CheckoutLayoutProcessor.php:

<?php
namespace <vendor>\<moduleName>\Plugin;

use Magento\Checkout\Block\Checkout\LayoutProcessor;
use Magento\Framework\App\Config\ScopeConfigInterface;
class CheckoutLayoutProcessor {

    protected $scopeConfig;
    public function __construct(ScopeConfigInterface $scopeConfig) {
        $this->scopeConfig = $scopeConfig;
    }

    public function beforeProcess(LayoutProcessor $layoutProcessor, $jsLayout)
    {
        // Do not render jsLayout of disabled payment methods (fixes Magento core bug)
        if ($this->scopeConfig->getValue('payment/free/active') == 0)
            unset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children']['free-payments']);
        if ($this->scopeConfig->getValue('payment/banktransfer/active') == 0)
            unset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children']['offline-payments']['methods']['banktransfer']);
        if ($this->scopeConfig->getValue('payment/cashondelivery/active') == 0)
            unset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children']['offline-payments']['methods']['cashondelivery']);
        if ($this->scopeConfig->getValue('payment/purchaseorder/active') == 0)
            unset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children']['offline-payments']['methods']['purchaseorder']);
        if ($this->scopeConfig->getValue('payment/checkmo/active') == 0)
            unset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children']['offline-payments']['methods']['checkmo']);
        return [$jsLayout];
    }
}

@che2on, I’m not promising this will reduce it to 1-2 seconds, but it should certainly help a little bit. Below is some sample code from our solution (it’s almost the same as @thlassche’s solution, so he earns the credits 😃). This isn’t a full module, just the important bits (and yes it is very ugly, the clean solution would be to get a list of all active payment methods and throw out the ones which aren’t in that list, currently we did it by hardcoding the payment methods which should be removed):

  • app/code/Vendor/FasterCheckout/etc/di.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
        <plugin name="vendor_fastercheckout_plugin_checkout_block_layoutprocessor"
                type="Vendor\FasterCheckout\Plugin\Checkout\Block\LayoutProcessor"/>
    </type>
</config>
  • app/code/Vendor/FasterCheckout/Plugin/Checkout/Block/LayoutProcessor.php:
<?php

namespace Vendor\FasterCheckout\Plugin\Checkout\Block;

use \Magento\Checkout\Block\Checkout\LayoutProcessor as CheckoutLayoutProcessor;

class LayoutProcessor
{
    public function afterProcess(CheckoutLayoutProcessor $subject, array $jsLayout)
    {
        $jsLayout = $this->removeUnusedPaymentMethods($jsLayout);

        return $jsLayout;
    }

    // tnx to https://github.com/magento/magento2/issues/4868#issuecomment-259244034
    private function removeUnusedPaymentMethods($jsLayout)
    {
        if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children']))
        {
            $paymentRenders = $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children'];

            /* !!! these are just examples, only remove the ones you don't use !!! */
            unset($paymentRenders['offline-payments']);
            unset($paymentRenders['authorizenet']);
            unset($paymentRenders['vault']);
            unset($paymentRenders['paypal-payments']);
            unset($paymentRenders['braintree']);

            $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children'] = $paymentRenders;
        }

        if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children']))
        {
            $paymentListChildren = $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children'];

            /* !!! these are just examples, only remove the ones you don't use !!! */
            unset($paymentListChildren['checkmo-form']);
            unset($paymentListChildren['banktransfer-form']);
            unset($paymentListChildren['cashondelivery-form']);
            unset($paymentListChildren['purchaseorder-form']);
            unset($paymentListChildren['authorizenet_directpost-form']);
            unset($paymentListChildren['payflowpro-form']);
            unset($paymentListChildren['payflow_link-form']);
            unset($paymentListChildren['payflow_advanced-form']);
            unset($paymentListChildren['hosted_pro-form']);
            unset($paymentListChildren['paypal_billing_agreement-form']);
            unset($paymentListChildren['braintree-form']);

            $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children']  = $paymentListChildren;
        }

        return $jsLayout;
    }
}

This is still not resolved.

Magento 2 Checkout JS:

  • Load all payment methods despite disabled or not
  • Load all countries and states despite disabled or not

This code is JSON fired inside the HTML rather than an API call - that code is around 957kb call it 1mb

When you JSON Lint fire the code it returns 2.1mb of JSON

Your developers need a kick up the ass in Ukraine.

Thanks for the insight @hostep a shame since were still on 2.0.4

In case anyone is interested in making the checkout faster in Magento 2.1.x, we have gotten it almost twice as fast as it was before, using:

  1. The commit MAGETWO-59685, you need to fiddle a bit with the diff from this commit to apply it cleanly on a stock Magento installation, but it is not that hard. (If you use Magento 2.1.2 you’ll also need the class Magento\Directory\Model\AllowedCountries which was introduced in Magento 2.1.3 and on which MAGETWO-59685 relies to work properly, if you use Magento 2.1.3 and up, you already have this class). This first part strips away all countries and regions which you don’t use in your shop resulting in a much more leaner json data structure.

  2. The second part is where you’ll need to strip away unused payment methods, which also results in an even smaller json data structure. I first tried backporting MAGETWO-60351 to Magento 2.1.x, but failed to do this, since it is built upon a whole bunch of other commits which aren’t released yet. So we took the approach which @LuckyLoek and @thlassche mentioned above and built a custom Plugin for Magento\Checkout\Block\Checkout\LayoutProcessor::process method, which strips out all unused payment methods from the $jsLayout array. In the following structures you can strip away unused payment methods:

  • $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['renders']['children']
  • $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children']

All of this results in the json data structure being significantly smaller then before and getting parsed much quicker in your browser, which makes the checkout as a whole much quicker to load.

Watch out: all the above was only tested properly on Magento 2.1.2, it should in theory work for 2.1.3 but this wasn’t tested properly. And 2.1.4 wasn’t tested at all.

Hope Magento is able to backport both MAGETWO-59685 and MAGETWO-60351 to Magento 2.1.x and is not keeping these fixes exclusively for Magento 2.2 and up.

Hope this helps someone 😃

This is a real serious issue, especially when you have many payment methods. One of our customers uses 2 payment providers which makes the total of payment methods 50+. Excluding countries and region reduces the request size, but even then the code looping these methods gets real slow. Total loading time took up to 30 seconds.

Since our customer only uses a total of 7 payment methods, I ‘fixed’ this by writing a plugin for ‘Magento\Checkout\Block\Checkout\LayoutProcessor’ in which I strip all payment methods that are not active for the current storeview. Now the page takes only 2 seconds to load instead of 30. This is however not a real solution to the problem.

The bug is still not fixed and enable bundling and merging hides only the problem.

Customers are already complaining about the bloated checkout page.