0

So I am building a mega menu and a separate set of templates for mobile. Both use the same set of element queries, which query two larger sections for pages and hierarchy. I am seeing quite slow load times (3-5 seconds) and I know it is down to the menus as when removed the site loads fast.

enter image description here

I have done Eager loading and tried to optimise the queries, but inevitably I am using them twice across each template, so 4 times in total.

What is another/better more efficient method to do this? I tried clone() function but I don't think i used it correctly.

For production, I could cache the menus so that would help, bit I would like to get to the bottom and improve the code and the performance of the queries initially.

Without positing lots of code, here is a summary of what I am doing.

First I setup two queries for the two main areas of the site.

  {% set forIndividualsNavigation = craft.entries
    .section('servicesBusiness')
    .with('children') %}
  
  {% set forBusinessNavigation = craft.entries
    .section('servicesPersonal')
    .with('children') %}

Then within my mobile navigation templates, I have built a slide menu pattern that each level another menu slides in....

enter image description here

This uses alpineJS and is setup like so in terms of queries.

 {% for page in forIndividualsNavigation.level(1).all() %}
    <button
      class="hover:text-black hover:text-opacity-80 focus:text-brand-500 focus:outline-none // w-full flex items-center justify-between transition"
    >
      {{ page.title }}
      {{ svg('@icon/bx-line/bx-right-arrow-alt.svg')|attr({ class:"w-7 h-7 flex-shrink-0 fill-current ml-2" }) }}
    </button>

<div
  x-cloak
  x-show="{{ _xShow }}"
  x-transition:enter="transition transform ease-out duration-200"
  x-transition:enter-start="opacity-0 translate-x-full"
  x-transition:enter-end="opacity-100 translate-x-0"
  x-transition:leave="transition transform ease-in duration-300"
  x-transition:leave-start="opacity-100 translate-x-0"
  x-transition:leave-end="opacity-0 translate-x-full"
  @click.away="{{ _xClose }}"
  class="{{ panelClasses }}"
>
  <button
    @click="{{ _xClose }}"
    class="hover:underline focus:underline focus:outline-none // flex items-center transition mb-8"
  >
    {{ svg('@icons/bx-line/bx-arrow-back.svg')|attr({ class:"w-5 h-5 mr-1 flex-shrink-0 fill-current" }) }}
    <span class="text-sm">Back</span>
  </button>
 div>
                <a
                  href="{{ page.url }}"
                  class="bg-business-900 p-4 -mx-6 font-semibold text-lg mb-2 block"
                >
                  {{ page.title }}
                </a>
                
                <ul class="divide-y divide-white divide-opacity-30 -mx-1">
                  {% for page in page.descendants.orderBy('title ASC').all() %}
                    <li>
                      <a href="{{ page.url() }}"
                         class="text-white hover:text-black hover:text-opacity-80 hover:underline block py-3 px-1.5"
                      >
                        {{ page.title }}
                      </a>
                    </li>
                  {% endfor %}
                  
                  <li>
                    <a href="{{ page.url }}"
                       class="text-sm text-black text-opacity-80 hover:underline flex justify-between py-3 px-1.5 // border-t-2 border-white border-opacity-30"
                    >
                      Explore our {{ page.title }} practice
                      {{ svg('@icon/bx-line/bx-right-arrow-alt.svg')|attr({ class:"w-5 h-5 flex-shrink-0 fill-current ml-2" }) }}
                    </a>
                  </li>
                </ul>
              </div>
</div>

Then the megu menus are setup like so: enter image description here

{% for page in forIndividualsNavigation.level(1).all() %}
  <button
    class="text-base font-medium text-white flex px-1.5 w-full focus:outline-none"
  >
    <div class="w-5 h-5 flex-shrink-0 mr-2 mt-px" :class="childMenuIsOpen == {{ page.id }} ? 'bg-current ' : 'border border-current'"></div>
    {{ page.title }}
  </button>
  
  <div class="lg:px-4 2xl:px-8 mb-14 pt-3">
    {% for page in forIndividualsNavigation.level(1).all() %}
      <nav
      >
        <div class="flex">
          <div class="w-14">
            <div class="w-10 h-10  flex-shrink-0 bg-business-500 mb-2"></div>
          </div>
          
          <div class="flex-1 pt-2">
            <h3 class="font-semibold text-lead mb-3">
              <a href="{{ page.url() }}" class="hover:underline">{{ page.title }}</a>
            </h3>
            <ul class="text-15 divide-y divide-gray-400 divide-opacity-30 -mx-1">
              {% for page in page.descendants.orderBy('title ASC').all() %}
                <li>
                  <a href="{{ page.url() }}"
                     class="text-white text-opacity-60 hover:text-business-500 hover:bg-white hover:bg-opacity-10 hover:underline flex justify-between py-3 px-3"
                  >
                    {{ page.title }}
                    {{ svg('@icon/bx-line/bx-right-arrow-alt.svg')|attr({ class:"w-5 h-5 flex-shrink-0 fill-current ml-2" }) }}
                  </a>
                </li>
              {% endfor %}
              
              <li>
                <a href="{{ page.url }}" class="text-base text-business-500 hover:underline flex justify-between py-3 px-1.5 // border-t-2 border-gray-400 border-opacity-30">
                  Explore our {{ page.title }} practice
                  {{ svg('@icon/bx-line/bx-right-arrow-alt.svg')|attr({ class:"w-5 h-5 flex-shrink-0 fill-current ml-2 mr-2" }) }}
                </a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    {% endfor %}
  </div>
{% endfor %}

Please note that the code above is reduced as I have removed some divs and layout code to illustrate more the queries taking place and you can see from the screenshots why there are some duplicated query, as the layout is different and it toggles the child navigation as you click/hover.

3

Since you have two sections, you will need your two initial queries, however, in your example, although you are eager loading the children (with(children)), you are querying them a second time when doing page.descendants.all().

Because you are doing this for every parent, things are adding up and you are hiting this high query count and speed issue.

First, your initial query should be:

{% set forIndividualsNavigation = craft.entries
    .section('servicesBusiness')
    .level(1)
    .with('children')
    .all() %}

Here, you fetch level(1) entries only and eager loading children on them. If you had more than 2 levels, you could use:

{% set forIndividualsNavigation = craft.entries
    .section('servicesBusiness')
    .level(1)
    .with('children.children')
    .all() %}

With this in place, your loop will look like below - I have stripped it down to a minimum so it can help others too but you should be able to adjust on your side:

<ul>
{# Loop over level 1 entries only #}
{% for page in forIndividualsNavigation %}
    <li><a href="{{ page.url }}">{{ page.title }}</a>
    {# Check if the current page has children, if so display #}
    {% if page.children %}
    <ul>
        {# Children are eager loaded so no need for `all()` again #}
        {% for page in page.children %}
            <li>
                <a href="{{ page.url }}">{{ page.title }}</a>
            </li>
        {% endfor %}
    </ul>
    {% endif %}
    </li>
{% endfor %}
</ul>

Try this and you should see a drop in queries and a speed increase :)

If you need to order the children the way you were doing it in your original post, you can pass custom params on the eager loaded elements like so:

{% set forIndividualsNavigation = craft.entries
    .section('servicesBusiness')
    .level(1)
    .with([
        ['children', { orderBy: "title ASC" }]
    ])
    .all() %}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.