fix: Alpine.js defer race condition — blank pages on first load
Some checks failed
CI / ruff (push) Successful in 9s
CI / dependency-scanning (push) Successful in 29s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
CI / pytest (push) Failing after 46m20s
CI / validate (push) Successful in 24s

Dynamic script creation (document.createElement) ignores the defer
attribute per HTML spec — scripts are async regardless. Alpine.js CDN
loaded fast and auto-initialized before page scripts had executed,
causing ReferenceError for x-data functions (adminStores, dark,
isSideMenuOpen, etc.) and blank pages.

Fix: Replace dynamic script creation with static <script defer> tags
and move extra_scripts block BEFORE Alpine.js in all 4 base templates
(admin, store, merchant, storefront). Alpine.js is now always the last
deferred script, ensuring all page functions are defined before it
auto-initializes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 11:22:29 +01:00
parent 30c4593e0f
commit 2b29867093
4 changed files with 23 additions and 83 deletions

View File

@@ -144,35 +144,20 @@
<!-- 8a. Alpine.js Collapse Plugin (must load before Alpine) --> <!-- 8a. Alpine.js Collapse Plugin (must load before Alpine) -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.13.3/dist/cdn.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.13.3/dist/cdn.min.js"></script>
<!-- 8b. Alpine.js v3 with CDN fallback (with defer) --> <!-- 8b. OPTIONAL: Chart.js with CDN fallback (loaded on demand via block) -->
<script>
(function() {
var script = document.createElement('script');
script.defer = true;
script.src = 'https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js';
script.onerror = function() {
console.warn('Alpine.js CDN failed, loading local copy...');
var fallbackScript = document.createElement('script');
fallbackScript.defer = true;
fallbackScript.src = '{{ url_for("static", path="shared/js/lib/alpine.min.js") }}';
document.head.appendChild(fallbackScript);
};
document.head.appendChild(script);
})();
</script>
<!-- 9. OPTIONAL: Chart.js with CDN fallback (loaded on demand via block) -->
{% block chartjs_script %}{% endblock %} {% block chartjs_script %}{% endblock %}
<!-- 10. OPTIONAL: Flatpickr with CDN fallback (loaded on demand via block) --> <!-- 8c. OPTIONAL: Flatpickr with CDN fallback (loaded on demand via block) -->
{% block flatpickr_script %}{% endblock %} {% block flatpickr_script %}{% endblock %}
<!-- 11. OPTIONAL: Quill with CDN fallback (loaded on demand via block) --> <!-- 8d. OPTIONAL: Quill with CDN fallback (loaded on demand via block) -->
{% block quill_script %}{% endblock %} {% block quill_script %}{% endblock %}
<!-- 12. LAST: Page-specific scripts --> <!-- 9. Page-specific scripts (MUST load before Alpine.js) -->
{% block extra_scripts %}{% endblock %} {% block extra_scripts %}{% endblock %}
<!-- 10. LAST: Alpine.js v3 (must be last defer script — auto-initializes on load) -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js"
onerror="var s=document.createElement('script');s.defer=true;s.src='{{ url_for('static', path='shared/js/lib/alpine.min.js') }}';document.head.appendChild(s);"></script>
</body> </body>
</html> </html>

View File

@@ -71,26 +71,11 @@
<!-- 5. FIFTH: API Client (depends on Utils) --> <!-- 5. FIFTH: API Client (depends on Utils) -->
<script defer src="{{ url_for('static', path='shared/js/api-client.js') }}"></script> <script defer src="{{ url_for('static', path='shared/js/api-client.js') }}"></script>
<!-- 6. SIXTH: Alpine.js v3 with CDN fallback (with defer) --> <!-- 6. Page-specific scripts (MUST load before Alpine.js) -->
<script>
(function() {
var script = document.createElement('script');
script.defer = true;
script.src = 'https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js';
script.onerror = function() {
console.warn('Alpine.js CDN failed, loading local copy...');
var fallbackScript = document.createElement('script');
fallbackScript.defer = true;
fallbackScript.src = '{{ url_for("static", path="shared/js/lib/alpine.min.js") }}';
document.head.appendChild(fallbackScript);
};
document.head.appendChild(script);
})();
</script>
<!-- 7. LAST: Page-specific scripts -->
{% block extra_scripts %}{% endblock %} {% block extra_scripts %}{% endblock %}
<!-- 7. LAST: Alpine.js v3 (must be last defer script — auto-initializes on load) -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js"
onerror="var s=document.createElement('script');s.defer=true;s.src='{{ url_for('static', path='shared/js/lib/alpine.min.js') }}';document.head.appendChild(s);"></script>
</body> </body>
</html> </html>

View File

@@ -94,26 +94,11 @@
<!-- 7. SEVENTH: Upgrade Prompts (depends on API Client, registers with Alpine) --> <!-- 7. SEVENTH: Upgrade Prompts (depends on API Client, registers with Alpine) -->
<script defer src="{{ url_for('billing_static', path='shared/js/upgrade-prompts.js') }}"></script> <script defer src="{{ url_for('billing_static', path='shared/js/upgrade-prompts.js') }}"></script>
<!-- 8. EIGHTH: Alpine.js v3 with CDN fallback (with defer) --> <!-- 8. Page-specific scripts (MUST load before Alpine.js) -->
<script>
(function() {
var script = document.createElement('script');
script.defer = true;
script.src = 'https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js';
script.onerror = function() {
console.warn('Alpine.js CDN failed, loading local copy...');
var fallbackScript = document.createElement('script');
fallbackScript.defer = true;
fallbackScript.src = '{{ url_for("static", path="shared/js/lib/alpine.min.js") }}';
document.head.appendChild(fallbackScript);
};
document.head.appendChild(script);
})();
</script>
<!-- 9. LAST: Page-specific scripts -->
{% block extra_scripts %}{% endblock %} {% block extra_scripts %}{% endblock %}
<!-- 9. LAST: Alpine.js v3 (must be last defer script — auto-initializes on load) -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js"
onerror="var s=document.createElement('script');s.defer=true;s.src='{{ url_for('static', path='shared/js/lib/alpine.min.js') }}';document.head.appendChild(s);"></script>
</body> </body>
</html> </html>

View File

@@ -380,28 +380,13 @@
{# 6. API Client #} {# 6. API Client #}
<script defer src="{{ url_for('static', path='shared/js/api-client.js') }}"></script> <script defer src="{{ url_for('static', path='shared/js/api-client.js') }}"></script>
{# 7. Alpine.js with CDN fallback (deferred - loads last) #} {# 7. Page-specific JavaScript (MUST load before Alpine.js) #}
<script>
(function() {
var script = document.createElement('script');
script.defer = true;
script.src = 'https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js';
script.onerror = function() {
console.warn('Alpine.js CDN failed, loading local copy...');
var fallbackScript = document.createElement('script');
fallbackScript.defer = true;
fallbackScript.src = '{{ url_for("static", path="shared/js/lib/alpine.min.js") }}';
document.head.appendChild(fallbackScript);
};
document.head.appendChild(script);
})();
</script>
{# 8. Page-specific JavaScript #}
{% block extra_scripts %}{% endblock %} {% block extra_scripts %}{% endblock %}
{# 8. LAST: Alpine.js (must be last defer script — auto-initializes on load) #}
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js"
onerror="var s=document.createElement('script');s.defer=true;s.src='{{ url_for('static', path='shared/js/lib/alpine.min.js') }}';document.head.appendChild(s);"></script>
{# Toast notification container #} {# Toast notification container #}
<div id="toast-container" class="fixed bottom-4 right-4 z-50"></div> <div id="toast-container" class="fixed bottom-4 right-4 z-50"></div>