Live site issue
August 21, 2025 · AI Conversations
but on live site its working fine
we are using sliverstripe ddev setup
That’s why `/themes/template/css/custom.css` 404s locally but works on your live box.
### Fix options (pick one)
1) **Use the correct public path**
<link rel="stylesheet" href="/resources/themes/template/css/custom.css">
2) **Let SilverStripe include it (recommended)**
– In a template:
<% require themedCSS('custom') %> <!-- loads themes/template/css/custom.css -->
– Or in PHP:
use SilverStripeViewRequirements;
Requirements::themedCSS('custom');
### Make sure assets are exposed
If you still get 404s, expose/symlink theme resources to `public/resources`:
ddev ssh
composer vendor-expose # or: composer vendor-expose symlink
# then clear caches
vendor/bin/sake dev/build flush=all || true
(Alternatively just hit any page with `?flush`.)
### Quick checklist
– File exists at `themes/template/css/custom.css`.
– `app/_config/theme.yml` has:
SilverStripeViewSSViewer:
themes:
- 'template'
- '$default'
– After changes: `?flush` once.
This will make your CSS load locally just like it does on live.
### Vanilla JS (safe for partial/AJAX loads too)
<script>
(function () {
// what you want to run for each matching element
function initApartmentUploadStep(el) {
if (el.dataset.apartmentInit === "1") return; // avoid re-init
el.dataset.apartmentInit = "1";
// --- your logic here ---
console.log("Init apartment-upload-step", el);
}
// run on current DOM
function scan() {
document.querySelectorAll(".apartment-upload-step").forEach(initApartmentUploadStep);
}
// run on initial load
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", scan);
} else {
scan();
}
// optional: watch for elements added later (AJAX, PJAX, Inertia, etc.)
const mo = new MutationObserver((mutations) => {
for (const m of mutations) {
m.addedNodes.forEach((node) => {
if (!(node instanceof Element)) return;
if (node.matches(".apartment-upload-step")) initApartmentUploadStep(node);
node.querySelectorAll?.(".apartment-upload-step").forEach(initApartmentUploadStep);
});
}
});
mo.observe(document.documentElement, { childList: true, subtree: true });
})();
</script>
### jQuery version (if you already load jQuery)
<script>
(function ($) {
function init($el) {
if ($el.data("apartmentInit")) return;
$el.data("apartmentInit", true);
// --- your logic here ---
console.log("Init apartment-upload-step", $el[0]);
}
function scan(ctx) {
$(ctx).find(".apartment-upload-step").each(function(){ init($(this)); });
}
$(scan); // DOM ready
// observe later DOM changes too
new MutationObserver(function (muts) {
muts.forEach(function (m) {
$(m.addedNodes).each(function () {
if (this.nodeType !== 1) return;
const $n = $(this);
if ($n.is(".apartment-upload-step")) init($n);
scan(this);
});
});
}).observe(document.documentElement, { childList: true, subtree: true });
})(jQuery);
</script>
#### Notes
– Uses a per-element flag (`data-apartment-init`) to prevent duplicate init.
– Handles elements added after load (useful if SilverStripe renders via partials/AJAX, or you navigate with PJAX/Inertia).
– If you only need the simple case (no dynamic injection), you can delete the `MutationObserver` block.
document.addEventListener("DOMContentLoaded", function () {
if (document.querySelector(".apartment-upload-step")) {
// run your code here
console.log("Page has apartment-upload-step");
}
});
That’s it ✅ – the code inside will only run if an element with `.apartment-upload-step` is present in the DOM.
Do you want me to also give you the **jQuery one-liner** version?