Beschikbaar voor nieuwe Shopify-projecten

Native bouwenTutorial

Dawn uitbreiden zonder app, deel 2: FAQ, verzendbalk en bezorgstatus

Diek ThunnissenDiek Thunnissen8 sep 202610 min leestijd

Een FAQ met categorieën, FAQ-schema voor Google, een verzendbalk die meetelt met de winkelwagen en een bezorgstatus op de productpagina. Vier conversie-onderdelen waar de app store een abonnement voor rekent, als eigen code in je thema.

De meeste conversie-apps doen iets kleins. Een balk die zegt hoeveel je nog moet bestellen voor gratis verzending. Een blokje met de verwachte bezorgdatum. Een FAQ die openklapt. Voor elk van die dingen betaal je een abonnement en laad je een script van een derde partij op de pagina waar je geld verdient.

In deel 1 stonden de layout-onderdelen: banners, subcollecties en breadcrumbs. Dit deel gaat over de onderdelen die direct aan conversie raken. Zelfde aanpak: eigen section of custom liquid block, eigen bestand, instellingen in de theme editor, niets aanpassen in de bestanden van Dawn zelf.

FAQ-sectie met categorieën

Een FAQ-pagina met twintig vragen onder elkaar leest niemand. Met categorieën links (Bestellen, Verzenden, Retourneren) en de vragen van de gekozen categorie rechts, blijft het overzichtelijk en vinden klanten hun antwoord zonder te scrollen. Antwoorden klappen pas open na een klik. Op mobiel worden de categorieën een rij knoppen boven de vragen.

Maak sections/dt-faq.liquid. Alles zit in één bestand: de markup, de styling en een klein stuk JavaScript zonder afhankelijkheden.

319 regels codeToon code +
{% comment %}
  Basis FAQ Section  - Simplele Versie
  This version uses inline JavaScript for maximum reliability
{% endcomment %}
<div class="faq-section">
  <div class="faq-container">
    <!-- Category navigation -->
    <div class="faq-sidebar">
      <ul class="faq-categories">
        {%- for block in section.blocks -%}
          {%- if block.type == 'category' -%}
            <li class="faq-category-item{% if forloop.first %} active{% endif %}">
              <button class="faq-category-button" onclick="showCategory('{{ block.id }}')">
                {{ block.settings.category_name }}
              </button>
            </li>
          {%- endif -%}
        {%- endfor -%}
      </ul>
    </div>
    <!-- FAQ content -->
    <div class="faq-content">
      {%- for block in section.blocks -%}
        {%- if block.type == 'category' -%}
          <div id="category-{{ block.id }}" class="faq-category-content{% if forloop.first %} active{% endif %}">
            <h2 class="faq-category-title">{{ block.settings.category_name }}</h2>
            <div class="faq-items">
              {%- for question_block in section.blocks -%}
                {%- if question_block.type == 'question' and question_block.settings.category_tag == block.settings.category_name -%}
                  <div class="faq-item" {{ question_block.shopify_attributes }}>
                    <button class="faq-question" onclick="toggleQuestion(this)">
                      <span class="faq-question-text">{{ question_block.settings.question }}</span>
                      <span class="faq-icon">+</span>
                    </button>
                    <div class="faq-answer">
                      <div class="faq-answer-content">
                        {{ question_block.settings.answer }}
                      </div>
                    </div>
                  </div>
                {%- endif -%}
              {%- endfor -%}
            </div>
          </div>
        {%- endif -%}
      {%- endfor -%}
    </div>
  </div>
</div>
<style>
/* Basic styles for layout */
.faq-section {
  margin: 60px 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: {{ section.settings.text_color }};
  background-color: {{ section.settings.background_color }};
}
.faq-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
  display: flex;
  gap: 40px;
}
/* Sidebar styles */
.faq-sidebar {
  width: 25%;
  min-width: 200px;
}
.faq-categories {
  list-style: none;
  padding: 0;
  margin: 0;
}
.faq-category-item {
  margin-bottom: 10px;
}
.faq-category-button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 12px 15px;
  background: none;
  border: none;
  border-left: 3px solid transparent;
  font-size: 16px;
  font-weight: 500;
  cursor: pointer;
  color: {{ section.settings.text_color }};
}
.faq-category-item.active .faq-category-button {
  border-left-color: {{ section.settings.accent_color }};
  color: {{ section.settings.accent_color }};
  background-color: rgba(0, 0, 0, 0.03);
}
/* Content styles */
.faq-content {
  flex: 1;
}
.faq-category-content {
  display: none;
}
.faq-category-content.active {
  display: block;
}
.faq-category-title {
  margin-top: 0;
  margin-bottom: 30px;
  font-size: 24px;
  font-weight: 600;
  color: {{ section.settings.text_color }};
}
/* FAQ items */
.faq-items {
  border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.faq-item {
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.faq-question {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  text-align: left;
  padding: 20px 0;
  background: none;
  border: none;
  font-size: 18px;
  font-weight: 500;
  cursor: pointer;
  color: {{ section.settings.text_color }};
}
.faq-question-text {
  flex: 1;
}
.faq-icon {
  font-size: 20px;
  margin-left: 10px;
}
.faq-question.active {
  color: {{ section.settings.accent_color }};
}
.faq-question.active .faq-icon {
  content: "-";
}
.faq-answer {
  display: none;
  padding: 0 0 20px;
}
.faq-answer.active {
  display: block;
}
.faq-answer-content {
  line-height: 1.6;
}
/* Mobile styles */
@media screen and (max-width: 768px) {
  .faq-container {
    flex-direction: column;
  }
  .faq-sidebar {
    width: 100%;
  }
  .faq-categories {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
  }
  .faq-category-button {
    border-left: none;
    border-bottom: 3px solid transparent;
    padding: 10px 15px;
  }
  .faq-category-item.active .faq-category-button {
    border-left-color: transparent;
    border-bottom-color: {{ section.settings.accent_color }};
  }
}
</style>
<script>
  function showCategory(categoryId) {
    // Hide all category contents
    var categoryContents = document.querySelectorAll('.faq-category-content');
    for (var i = 0; i < categoryContents.length; i++) {
      categoryContents[i].classList.remove('active');
    }
    // Show the selected category content
    var selectedCategory = document.getElementById('category-' + categoryId);
    if (selectedCategory) {
      selectedCategory.classList.add('active');
    }
    // Update category button styles
    var categoryItems = document.querySelectorAll('.faq-category-item');
    for (var i = 0; i < categoryItems.length; i++) {
      categoryItems[i].classList.remove('active');
    }
    // Find the clicked button's parent and add active class
    var clickedButton = event.currentTarget;
    var parentItem = clickedButton.parentNode;
    parentItem.classList.add('active');
  }
  function toggleQuestion(button) {
    // Toggle active class on the button
    button.classList.toggle('active');
    // Get the answer element (next sibling)
    var answer = button.nextElementSibling;
    answer.classList.toggle('active');
    // Update the icon
    var icon = button.querySelector('.faq-icon');
    if (button.classList.contains('active')) {
      icon.textContent = '-';
    } else {
      icon.textContent = '+';
    }
  }
</script>
{% schema %}
{
  "name": "👨‍💻 DT FAQ",
  "tag": "section",
  "class": "section",
  "settings": [
    {
      "type": "color",
      "id": "background_color",
      "label": "Background Color",
      "default": "#ffffff"
    },
    {
      "type": "color",
      "id": "text_color",
      "label": "Text Color",
      "default": "#333333"
    },
    {
      "type": "color",
      "id": "accent_color",
      "label": "Accent Color",
      "default": "#0066cc"
    }
  ],
  "blocks": [
    {
      "type": "category",
      "name": "FAQ Category",
      "limit": 10,
      "settings": [
        {
          "type": "text",
          "id": "category_name",
          "label": "Category Name",
          "default": "Category"
        }
      ]
    },
    {
      "type": "question",
      "name": "FAQ Question",
      "settings": [
        {
          "type": "text",
          "id": "question",
          "label": "Question",
          "default": "Frequently asked question"
        },
        {
          "type": "richtext",
          "id": "answer",
          "label": "Answer",
          "default": "<p>Provide the answer to your FAQ here. You can use basic HTML formatting.</p>"
        },
        {
          "type": "text",
          "id": "category_tag",
          "label": "Category",
          "info": "Enter the exact category name this question belongs to",
          "default": "Category"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "FAQ with Categories",
      "blocks": [
        {
          "type": "category",
          "settings": {
            "category_name": "Orders & Shipping"
          }
        },
        {
          "type": "category",
          "settings": {
            "category_name": "Returns & Exchanges"
          }
        },
        {
          "type": "question",
          "settings": {
            "question": "How long will it take for my order to arrive?",
            "answer": "<p>Orders typically ship within 1-2 business days. Delivery times vary by location but generally take 3-5 business days after shipping.</p>",
            "category_tag": "Orders & Shipping"
          }
        },
        {
          "type": "question",
          "settings": {
            "question": "What is your return policy?",
            "answer": "<p>We offer a 30-day return policy for most items. Products must be unused and in original packaging.</p>",
            "category_tag": "Returns & Exchanges"
          }
        }
      ]
    }
  ]
}
{% endschema %}

Voeg de section via de theme editor toe aan je FAQ-pagina. Daarna voeg je blokken toe: eerst een FAQ Category per categorie, dan per vraag een FAQ Question. De koppeling tussen vraag en categorie loopt via het veld Category in het vraag-blok, en dat moet letterlijk gelijk zijn aan de categorienaam. Dat is meteen de eerste plek om te kijken als een vraag niet verschijnt. Klappen vragen niet uit, kijk dan in de browserconsole of een ander script conflicteert.

FAQ-schema voor Google

Een FAQ op de pagina helpt de klant. Een FAQ als JSON-LD in de code helpt Google dezelfde vragen te tonen als uitklapbare blokken onder je zoekresultaat. Daarmee wordt je resultaat groter en valt het meer op. Het schema vertaalt vraag en antwoord naar een vorm die Google leest.

Twee regels vooraf. Eén H1 per pagina, de vragen zelf in H2 of H3. En de antwoorden in het schema moeten overeenkomen met wat zichtbaar op de pagina staat, anders negeert Google het of erger.

Het schema zelf genereer je met een gratis schema-generator of door de tekst in een taalmodel te plakken en om FAQ-schema in JSON-LD te vragen. Controleer de output, en test hem daarna in de Rich Results Test van Google. Plakken doe je in de HTML-modus van de editor (het icoon met de haken), niet in de visuele modus, want die beschadigt de code. Onderaan de pagina, tussen script-tags.

<script type="application/ld+json">
{
  ... jouw FAQ JSON hier ...
}
</script>

Test na elke thema-update of app-installatie opnieuw. Beide kunnen je schema breken, en dat zie je dan pas terug als foutmeldingen in Search Console.

Dynamische gratis-verzendbalk

Je bent nog 23,50 verwijderd van gratis verzending. Die ene zin verhoogt de orderwaarde, mits hij klopt en meebeweegt met de winkelwagen zonder paginaverversing. Deze section leest de cart via /cart.js, luistert naar de cart-events van Dawn en ververst voor de zekerheid elke vijf seconden. Boven de drempel wisselt hij naar een felicitatiebericht.

Maak sections/dt-gratis-verzending.liquid.

201 regels codeToon code +
{% schema %}
{
  "name": "DT Gratis Verzending",
  "settings": [
    {
      "type": "color",
      "id": "background_color",
      "label": "Background Color",
      "default": "#212121"
    },
    {
      "type": "color",
      "id": "text_color",
      "label": "Text Color",
      "default": "#ffffff"
    },
    {
      "type": "number",
      "id": "free_shipping_threshold",
      "label": "Free Shipping Threshold (in EUR)",
      "default": 150
    },
    {
      "type": "text",
      "id": "progress_message",
      "label": "Progress Message",
      "default": "Je bent nog €[amount] verwijderd van gratis verzending*"
    },
    {
      "type": "text",
      "id": "success_message",
      "label": "Success Message",
      "default": "Gefeliciteerd! Je bestelling komt in aanmerking voor gratis verzending"
    },
    {
      "type": "text",
      "id": "static_message",
      "label": "Static Message (optional)",
      "default": "Gratis verzending voor alle bestellingen vanaf €150 in Nederland!*"
    },
    {
      "type": "checkbox",
      "id": "use_static_message",
      "label": "Use Static Message (instead of dynamic)",
      "default": false
    }
  ],
  "presets": [
    {
      "name": "DT Gratis Verzending",
      "category": "Promotional"
    }
  ]
}
{% endschema %}

<style>
.dynamic-shipping-bar {
  background-color: {{ section.settings.background_color }};
  color: {{ section.settings.text_color }};
  text-align: center;
  padding: 10px 20px;
  font-size: 14px;
  width: 100%;
}

.shipping-success-message,
.shipping-progress-message,
.shipping-static-message {
  transition: opacity 0.3s ease;
}

.hidden {
  display: none;
}
</style>

<div class="dynamic-shipping-bar" id="dynamic-shipping-bar">
  {% if section.settings.use_static_message %}
    <div class="shipping-static-message">
      {{ section.settings.static_message }}
    </div>
  {% else %}
    <div class="shipping-success-message hidden">
      {{ section.settings.success_message }}
    </div>
    <div class="shipping-progress-message">
      {{ section.settings.progress_message }}
    </div>
  {% endif %}
</div>

<script>
  (function() {
    // Configuratie-instellingen van de sectie ophalen
    const config = {
      threshold: {{ section.settings.free_shipping_threshold | json }},
      useStaticMessage: {{ section.settings.use_static_message | json }},
      progressMessage: {{ section.settings.progress_message | json }}
    };
    
    // Alleen actief als we niet in statische modus zitten
    if (!config.useStaticMessage) {
      // Initiële update bij pagina laden
      document.addEventListener('DOMContentLoaded', function() {
        updateShippingBar();
      });
      
      // Cart events voor verschillende Shopify thema's
      document.addEventListener('cart:refresh', updateShippingBar);
      document.addEventListener('cart:updated', updateShippingBar);
      
      // Directe Ajax cart implementaties
      if (typeof window.Shopify !== 'undefined' && window.Shopify.onCartUpdate) {
        const originalOnCartUpdate = window.Shopify.onCartUpdate;
        window.Shopify.onCartUpdate = function(cart) {
          if (typeof originalOnCartUpdate === 'function') {
            originalOnCartUpdate(cart);
          }
          updateShippingBarWithCart(cart);
        };
      }
      
      // Monitor cart form submissions en quantity wijzigingen
      monitorCartChanges();
    }
    
    function monitorCartChanges() {
      // Wacht even tot de pagina volledig is geladen
      setTimeout(function() {
        // Cart forms (toevoegen aan winkelwagen)
        document.querySelectorAll('form[action="/cart/add"]').forEach(form => {
          form.addEventListener('submit', function() {
            setTimeout(updateShippingBar, 500);
          });
        });
        
        // Quantity inputs
        document.querySelectorAll('input[name="updates[]"], [data-quantity-input]').forEach(input => {
          input.addEventListener('change', function() {
            setTimeout(updateShippingBar, 500);
          });
        });
        
        // Verwijder knoppen
        document.querySelectorAll('[data-cart-remove], .cart__remove, .cart-item__remove, [href*="/cart/change?"]').forEach(button => {
          button.addEventListener('click', function() {
            setTimeout(updateShippingBar, 500);
          });
        });
      }, 1000);
    }
    
    function updateShippingBar() {
      // Get current cart data through Shopify's cart API
      fetch('/cart.js')
        .then(response => response.json())
        .then(cart => {
          updateShippingBarWithCart(cart);
        })
        .catch(error => console.error('Error fetching cart:', error));
    }
    
    function updateShippingBarWithCart(cart) {
      const cartTotal = cart.total_price / 100; // Convert from cents to euros
      
      // Get elements
      const progressMessage = document.querySelector('.shipping-progress-message');
      const successMessage = document.querySelector('.shipping-success-message');
      
      // Calculate remaining amount to qualify for free shipping
      const remaining = Math.max(0, config.threshold - cartTotal);
      
      // Update message
      if (remaining > 0) {
        // Nog niet aan gratis verzending
        // Formatteer het bedrag
        const formattedRemaining = remaining.toFixed(2).replace('.', ',');
        
        // Vervang placeholder in originele template
        if (progressMessage) {
          progressMessage.textContent = config.progressMessage.replace('[amount]', formattedRemaining);
        }
        
        // Toon progress message, verberg success message
        if (progressMessage) progressMessage.classList.remove('hidden');
        if (successMessage) successMessage.classList.add('hidden');
      } else {
        // Komt in aanmerking voor gratis verzending!
        if (progressMessage) progressMessage.classList.add('hidden');
        if (successMessage) successMessage.classList.remove('hidden');
      }
    }
    
    // Periodieke refresh (voor het geval andere methoden niet werken)
    setInterval(updateShippingBar, 5000);
    
    // Start de initiële update
    updateShippingBar();
  })();
</script>

Voeg de section toe via de theme editor, meestal direct onder de header. De drempel, de kleuren en de teksten stel je daar in. In het voortgangsbericht is [amount] de placeholder voor het resterende bedrag. Wil je een vaste tekst zonder berekening, zet dan Use Static Message aan.

Blijft het bedrag staan, controleer dan eerst de placeholder in het bericht en daarna de browserconsole. Werkt de balk niet met een aangepaste ajax-cart, dan mist die cart de events waar de section naar luistert, en dan is de refresh van vijf seconden je vangnet. De drempel staat in deze versie als één getal in de section. Een drempel per land of klanttype uit één shop-veld, die ook de checkout en de kortingslogica leest, is de nieuwere aanpak, daar volgt een apart stuk over.

Bezorgstatus op de productpagina

Voor 17:00 besteld, vandaag verzonden, morgen of overmorgen in huis. Klanten willen dat weten vóór ze op de knop drukken, niet op de bedankpagina. Dit blok toont drie stappen met datums: bestelling geplaatst, verzonden, bezorgd. Het rekent zelf de verzenddag uit op basis van de cut-off en slaat weekenden over.

Dit is geen section maar een Custom liquid block. Open in de theme editor een productpagina, klik op de sectie waar het blok moet komen (onder de beschrijving of boven de knop), Add block, Custom liquid, en plak de code.

216 regels codeToon code +
<!-- BEGIN Custom Liquid -->
<div id="diek-order-tracker" class="diek-tracker-container">
  <style>
    #diek-order-tracker {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
      box-sizing: border-box;
      margin: 20px 0;
    }
    
    #diek-order-tracker * {
      box-sizing: border-box;
    }
    
    .diek-tracker-container {
      width: 100%;
      max-width: 100%;
    }
    
    .diek-tracker-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 8px;
    }
    
    .diek-tracker-title {
      font-size: 16px;
      font-weight: 600;
      color: #000000;
    }
    
    .diek-guarantee {
      display: flex;
      align-items: center;
      font-size: 12px;
      color: #000000;
      background-color: rgba(0, 0, 0, 0.1);
      padding: 4px 8px;
      border-radius: 4px;
    }
    
    .diek-guarantee i {
      margin-right: 5px;
    }
    
    .diek-tracker-blocks {
      display: flex;
      background-color: white;
      border-radius: 8px;
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
      overflow: hidden;
    }
    
    .diek-tracker-block {
      flex: 1;
      padding: 15px 10px;
      text-align: center;
      position: relative;
    }
    
    .diek-tracker-block:not(:last-child) {
      border-right: 1px solid #f0f0f0;
    }
    
    .diek-tracker-icon {
      color: #000000;
      margin-bottom: 8px;
      font-size: 22px;
    }
    
    .diek-tracker-label {
      color: #000000;
      font-weight: 600;
      font-size: 13px;
      margin-bottom: 5px;
    }
    
    .diek-tracker-date {
      color: #666;
      font-size: 12px;
    }
    
    .diek-tracker-note {
      background-color: rgba(0, 0, 0, 0.08);
      border-radius: 6px;
      padding: 8px 12px;
      font-size: 12px;
      color: #000000;
      margin-top: 10px;
    }
  </style>
  <div class="diek-tracker-header">
    <div class="diek-tracker-title">Uw bestelling</div>
    <div class="diek-guarantee">
      <i class="fas fa-check-circle"></i> Bezorggarantie
    </div>
  </div>
  <div class="diek-tracker-blocks">
    <div class="diek-tracker-block">
      <div class="diek-tracker-icon">
        <i class="fas fa-shopping-cart"></i>
      </div>
      <div class="diek-tracker-label">Bestelling geplaatst</div>
      <div id="diek-order-date" class="diek-tracker-date">Laden...</div>
    </div>
    
    <div class="diek-tracker-block">
      <div class="diek-tracker-icon">
        <i class="fas fa-truck"></i>
      </div>
      <div class="diek-tracker-label">Verzonden</div>
      <div id="diek-dispatch-date" class="diek-tracker-date">Laden...</div>
    </div>
    
    <div class="diek-tracker-block">
      <div class="diek-tracker-icon">
        <i class="fas fa-home"></i>
      </div>
      <div class="diek-tracker-label">Bezorgd</div>
      <div id="diek-delivery-date" class="diek-tracker-date">Laden...</div>
    </div>
  </div>
  
  <div class="diek-tracker-note">
    <i class="fas fa-info-circle"></i> Bestellingen op werkdagen voor 17:00 worden dezelfde dag verzonden.
  </div>
  <script>
    (function() {
      // Functie om de datum te formatteren naar 'DD MMM' formaat in het Nederlands
      function formatDate(date) {
        const months = ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'];
        const day = date.getDate();
        const month = months[date.getMonth()];
        return `${day} ${month}`;
      }
      // Functie om werkdagen toe te voegen (slaat weekend over)
      function addBusinessDays(date, days) {
        let result = new Date(date);
        let daysAdded = 0;
        
        while (daysAdded < days) {
          result.setDate(result.getDate() + 1);
          // Check of het geen weekend is (0 = zondag, 6 = zaterdag)
          if (result.getDay() !== 0 && result.getDay() !== 6) {
            daysAdded++;
          }
        }
        
        return result;
      }
      function updateDates() {
        try {
          // Huidige datum en tijd voor bestelling
          const today = new Date();
          
          // Verzendingsdatum bepalen
          let dispatchDate;
          const currentHour = today.getHours();
          const currentDay = today.getDay(); // 0 (zondag) tot 6 (zaterdag)
          const isBusinessDay = currentDay !== 0 && currentDay !== 6;
          const isBeforeCutoff = currentHour < 17;
          
          if (isBusinessDay && isBeforeCutoff) {
            // Voor 17:00 op een werkdag: vandaag verzonden
            dispatchDate = new Date(today);
          } else if (isBusinessDay && !isBeforeCutoff) {
            // Na 17:00 op een werkdag: morgen verzonden (mits geen weekend)
            dispatchDate = new Date(today);
            dispatchDate.setDate(dispatchDate.getDate() + 1);
            
            // Als morgen weekend is, dan maandag
            if (dispatchDate.getDay() === 0 || dispatchDate.getDay() === 6) {
              while (dispatchDate.getDay() === 0 || dispatchDate.getDay() === 6) {
                dispatchDate.setDate(dispatchDate.getDate() + 1);
              }
            }
          } else {
            // Weekend: verzending op maandag
            dispatchDate = new Date(today);
            while (dispatchDate.getDay() === 0 || dispatchDate.getDay() === 6) {
              dispatchDate.setDate(dispatchDate.getDate() + 1);
            }
          }
          
          // Levering is 1-2 werkdagen na verzending
          const deliveryStartDate = addBusinessDays(dispatchDate, 1);
          const deliveryEndDate = addBusinessDays(dispatchDate, 2);
          
          // Datums bijwerken
          document.getElementById('diek-order-date').textContent = formatDate(today);
          document.getElementById('diek-dispatch-date').textContent = formatDate(dispatchDate);
          document.getElementById('diek-delivery-date').textContent = 
            `${formatDate(deliveryStartDate)} - ${formatDate(deliveryEndDate)}`;
        } catch (e) {
          console.error("Error updating dates:", e);
        }
      }
      
      // Direct de datums bijwerken bij het laden
      updateDates();
    })();
  </script>
  
  <!-- Font Awesome alleen toevoegen als het nog niet geladen is -->
  <script>
    if (!document.getElementById('font-awesome-diek')) {
      var fa = document.createElement('link');
      fa.id = 'font-awesome-diek';
      fa.rel = 'stylesheet';
      fa.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css';
      fa.type = 'text/css';
      document.head.appendChild(fa);
    }
  </script>
</div>
<!-- END Custom Liquid -->

De cut-off staat op 17:00 in deze regel. Verander het getal naar jouw tijd.

const isBeforeCutoff = currentHour < 17;

De bezorgtermijn staat op één tot twee werkdagen na verzending. Verhoog het getal in deze regel als bezorging bij jou langer duurt.

const deliveryEndDate = addBusinessDays(dispatchDate, 2);

Twee kanttekeningen. De iconen komen van Font Awesome, dat het blok zelf laadt als het nog niet op de pagina staat. Wil je geen externe request, vervang dan de i-tags door emoji of inline SVG. En de datum wordt in de browser van de klant berekend, dus in de tijdzone van de klant. Voor een Nederlandse shop met Nederlandse klanten maakt dat niet uit, voor een shop die over tijdzones heen verkoopt wel.

Waar de grens ligt

Alle vier de onderdelen tonen iets, ze dwingen niets af. De verzendbalk zegt dat je gratis verzending krijgt, maar de verzendinstelling van Shopify bepaalt of dat ook gebeurt. De bezorgstatus belooft een datum die je fulfilment moet waarmaken. Zodra een regel echt afgedwongen moet worden, in de checkout, hoort hij in een Shopify Function. Tot die tijd is dit de goedkoopste en snelste manier om Dawn te laten doen wat de apps beloven.

Veelgestelde vragen.

Kan de verzendbalk een andere drempel per land tonen?
Niet in deze versie, de drempel is één getal in de section-instellingen. Voor een drempel per land of klanttype gebruik je een json-veld op de shop dat de balk, de checkout en de kortingslogica alle drie lezen. Dat is een andere opzet en krijgt een eigen artikel.
Gebruikt de bezorgstatus echte verzendgegevens?
Nee. Het blok rekent met een vaste regel: cut-off, werkdagen en een bezorgtermijn. Het weet niets van voorraad, vervoerder of feestdagen. Dat maakt het licht en betrouwbaar voor een shop met een vast verzendproces, en ongeschikt als je leverdatum per product of per vervoerder verschilt.
Moet het FAQ-schema op elke pagina staan?
Alleen op pagina's waar de vragen en antwoorden ook zichtbaar staan. Schema zonder zichtbare inhoud beschouwt Google als misleidend. Eén schema per pagina, met precies de vragen die erop staan.
Diek ThunnissenDiek ThunnissenFounder & lead developer. Bouwt, verbetert en migreert de Shopify-laag voor DTC- en B2B-merken. LinkedInPlan een gesprek

Technische Shopify-inzichten uit de praktijk.

Geen sales, wel techniek.