Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

The "defer" Attribute & Conditionally Activating Turbo

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Inspect element and go check out the head tag. Notice that all of our script elements live up here in the head with a defer attribute. That's on purpose. And this defer attribute comes from our configuration: config/packages/webpack_encore.yaml: script_attributes, defer

... lines 1 - 6
# Set attributes that will be rendered on all script and link tags
defer: true
# link_attributes:
... lines 11 - 31

The defer Attribute

The reason we placed our script tags up in the head element is... well, we learned why in the last chapter! By adding them here, they won't be re-executed on every Turbo visit.

But normally, adding script tags to the head is bad for performance. When your browser sees a script tag, it freezes the page rendering while it downloads the file and executes it. But by adding defer, the file is downloaded in the background and the page continues loading without waiting. Once the page finishes loading, then the JavaScript is executed. If you want to learn more about the defer attribute, we have a blog post about it on symfony.com: https://symfony.com/blog/moving-script-inside-head-and-the-defer-attribute

Anyways, here's the big takeaway about using Turbo Drive and JavaScript: to get it to work reliably, all of your JavaScript needs to be written in Stimulus. But that does not mean that you need to completely rewrite it. If you have a big block of JavaScript that works on an element, you can copy that code into the connect() method of a Stimulus controller, which is called each time a matching data-controller element is found. Often, the only change you need to make is to remove any document.ready() code and tweak your JavaScript to target this.element.

And... if you can't or don't want to use Stimulus, you can also tweak your code so that it's executed on each "Turbo page load", like by wrapping that code in a Turbo event, that's fired on each visit instead of using jQuery's document.ready() method. We'll talk about Turbo events later.

Completely Disabling Turbo

By the way, if you did need to disable Turbo for a specific link... or even for an entire section of the page, you can do that with a special data-turbo attribute. For example, to completely disable Turbo drive on your entire site, head over to base.html.twig. Find the body tag and add data-turbo="false".

... lines 1 - 11
{% endblock %}
<body data-turbo="false">
<div class="page-top">
<header class="header px-2">
... lines 17 - 84

Now, any link clicks or form submits inside of this element - which is everything - will not use Turbo drive. Check it out: refresh the page and click around. We are back to boring full page reloads. Boo.

To reenable Turbo Drive on a link or section, you can set the same attribute to true. For example, let's activate Drive for just the links up in the navbar. Find that element... it's this ul, and add data-turbo="true"

... lines 1 - 27
<ul class="navbar-nav" data-turbo="true">
<li class="nav-item">
<a class="nav-link" href="{{ path('app_cart') }}">
Shopping Cart ({{ count_cart_items() }})
... lines 35 - 84

Refresh again. When we click a category, it still triggers a full page reload. But if we click to go to the cart... that loaded with Drive! You can use this strategy to activate Turbo Drive on only some parts of your site that are ready.

Let's remove both of these to fully get Turbo Drive again.

... lines 1 - 12
<div class="page-top">
<header class="header px-2">
... lines 17 - 27
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ path('app_cart') }}">
Shopping Cart ({{ count_cart_items() }})
... lines 35 - 84

Next: we've activated Turbo Drive and gotten the no-page-reload goodness with zero changes to our Symfony code! That's... amazing! But... there is one tiny change that we will need to make to any pages that have a form.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=7.4.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "", //
        "doctrine/annotations": "^1.0", // 1.13.1
        "doctrine/doctrine-bundle": "^2.2", // 2.3.2
        "doctrine/orm": "^2.8", // 2.9.1
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "sensio/framework-extra-bundle": "^6.1", // v6.1.4
        "symfony/asset": "5.3.*", // v5.3.0-RC1
        "symfony/console": "5.3.*", // v5.3.0-RC1
        "symfony/dotenv": "5.3.*", // v5.3.0-RC1
        "symfony/flex": "^1.3.1", // v1.18.5
        "symfony/form": "5.3.*", // v5.3.0-RC1
        "symfony/framework-bundle": "5.3.*", // v5.3.0-RC1
        "symfony/property-access": "5.3.*", // v5.3.0-RC1
        "symfony/property-info": "5.3.*", // v5.3.0-RC1
        "symfony/proxy-manager-bridge": "5.3.*", // v5.3.0-RC1
        "symfony/runtime": "5.3.*", // v5.3.0-RC1
        "symfony/security-bundle": "5.3.*", // v5.3.0-RC1
        "symfony/serializer": "5.3.*", // v5.3.0-RC1
        "symfony/twig-bundle": "5.3.*", // v5.3.0-RC1
        "symfony/ux-chartjs": "^1.1", // v1.3.0
        "symfony/ux-turbo": "^1.3", // v1.3.0
        "symfony/ux-turbo-mercure": "^1.3", // v1.3.0
        "symfony/validator": "5.3.*", // v5.3.0-RC1
        "symfony/webpack-encore-bundle": "^1.9", // v1.11.2
        "symfony/yaml": "5.3.*", // v5.3.0-RC1
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.1
        "twig/intl-extra": "^3.2", // v3.3.0
        "twig/string-extra": "^3.3", // v3.3.1
        "twig/twig": "^2.12|^3.0" // v3.3.2
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.0
        "symfony/debug-bundle": "^5.2", // v5.3.0-RC1
        "symfony/maker-bundle": "^1.27", // v1.31.1
        "symfony/monolog-bundle": "^3.0", // v3.7.0
        "symfony/stopwatch": "^5.2", // v5.3.0-RC1
        "symfony/var-dumper": "^5.2", // v5.3.0-RC1
        "symfony/web-profiler-bundle": "^5.2", // v5.3.0-RC1
        "zenstruck/foundry": "^1.10" // v1.10.0