Widget not loading
Widget script tag is on the page but the search UI never appears — diagnose script load failure, missing container, CSP blocks, key issues, and Shadow DOM mounting.
If you embedded widget.js and the search box does not appear, work through this in order. Each check eliminates one cause.
Check 1: script actually loaded
Open browser dev tools → Network → filter for widget.js. You should see a 200. If you see:
| Status | Cause |
|---|---|
0 (failed) | CSP blocks script-src from app.aacsearch.com |
403 | Origin not allowed for the search key |
404 | Wrong path; widget.js lives under https://app.aacsearch.com/api/widget/widget.js |
Blocked by CORS | Should never happen — the script is served with permissive CORS; if it does, file a ticket |
For CSP, add app.aacsearch.com to your script-src directive:
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://app.aacsearch.com; img-src 'self' data: https:;"
/>Check 2: container exists at boot time
The widget mounts into the element identified by data-container. If the element does not exist when the script runs, mount silently fails.
<!-- ❌ Container after the script -->
<script src="https://app.aacsearch.com/api/widget/widget.js"
data-container="#aac-search"
data-api-key="ss_search_..."
data-index-slug="products"></script>
<div id="aac-search"></div>
<!-- ✅ Container before the script -->
<div id="aac-search"></div>
<script src="https://app.aacsearch.com/api/widget/widget.js"
data-container="#aac-search"
data-api-key="ss_search_..."
data-index-slug="products"></script>If the container is rendered by a SPA framework after first paint, defer initialization:
// React example — wait until the container is in the DOM
useEffect(() => {
const script = document.createElement("script");
script.src = "https://app.aacsearch.com/api/widget/widget.js";
script.dataset.container = "#aac-search";
script.dataset.apiKey = process.env.NEXT_PUBLIC_AACSEARCH_SEARCH_KEY!;
script.dataset.indexSlug = "products";
document.body.appendChild(script);
return () => document.body.removeChild(script);
}, []);Check 3: API key valid for this origin
The widget calls the public search endpoint on every keystroke. If the search key has allowedOrigins set, the storefront origin must be in that list.
In dev tools → Network → filter for /api/search/...:
| Status on first call | Diagnosis |
|---|---|
200 | All good — issue is elsewhere (rendering, CSS) |
403 origin_not_allowed | Add the storefront origin to the key — see Auth errors |
401 | Key revoked, expired, or wrong prefix — see Auth errors |
429 | Rate limit — see Rate limits |
404 index_not_found | data-index-slug does not match an existing index |
Check 4: Shadow DOM mount
The widget renders inside a Shadow DOM root attached to your container. If your CSS uses !important or a global selector that targets [shadow-root] ancestors, the widget can render but be invisible.
In dev tools → Elements → expand the container → look for #shadow-root (open). If it is there, the widget loaded. If the inner content has display: none or opacity: 0, your CSS is the culprit.
/* ❌ Common mistake */
* {
visibility: visible !important;
}
/* ✅ Scope to your own components */
.my-component * {
visibility: visible !important;
}Check 5: console errors
Browser console → look for [aacsearch] prefixed messages.
| Console message | Meaning |
|---|---|
[aacsearch] container "#aac-search" not found | Container missing — see Check 2 |
[aacsearch] missing data-api-key | data-api-key attribute not set on the script tag |
[aacsearch] failed to fetch translations | Network blocked the i18n bundle; widget falls back to English |
[aacsearch] failed to render: <error> | Bug in the storefront page; share the error in your ticket |
Check 6: ad-blocker / extension
A small but real cause: extensions like uBlock with custom rules sometimes block third-party scripts that match a generic pattern. Test in an incognito window with no extensions to rule this out.
Check 7: SPA route change
If the widget mounted on the homepage but disappeared after navigating to a product page, the SPA likely tore down the container. Re-initialize on each route change:
// React example — Next.js App Router
import { useEffect } from "react";
import { usePathname } from "next/navigation";
useEffect(() => {
window.AACSearch?.mount({
container: "#aac-search",
apiKey: process.env.NEXT_PUBLIC_AACSEARCH_SEARCH_KEY!,
indexSlug: "products",
});
return () => window.AACSearch?.unmount("#aac-search");
}, [usePathname()]);Fix matrix
| Diagnosis | Fix |
|---|---|
| CSP blocks script | Add app.aacsearch.com to script-src |
| Container missing | Move <div> above <script>, or re-mount on route change |
| Key wrong / origin not allowed | See Auth errors |
| Index slug typo | Use the actual slug from Search → Indexes |
| Custom CSS hides widget | Scope your global selectors to your own components |
| Extension blocks script | Whitelist app.aacsearch.com in the user's ad-blocker, or document this as a known caveat |
Diagnostics packet
| Field | Notes |
|---|---|
| Organization ID | required |
| Index slug | required |
| Storefront URL | required (so we can reproduce) |
| Browser + version | required |
| Console errors | screenshot or text |
| Network log | widget.js row + first /api/search/... row |
| CSP header in use | from response headers of the storefront page |
Related
Slow search
Search latency above expected — diagnose cold starts, oversized `perPage`, complex `filterBy`, missing facets, and upstream search engine pressure.
Ingest failures
Documents pushed to AACsearch but never appear in search — diagnose buffer queue lag, schema mismatch, validation errors, and reindex pressure.