Quick diagnosis
Server-side rendering errors
Error: “customElements is not defined”
Cause: Primer code is running on the server where Web Components API doesn’t exist. Solution: EnsureloadPrimer() is called only in client-side lifecycle methods.
- Next.js / React
- Nuxt 3
- SvelteKit
Error: “window is not defined”
Cause: Code is accessing browser globals during server-side rendering. Solution: Add environment checks before accessing browser APIs:Card form issues
Duplicate card forms
Cause: Using both<primer-card-form> and <primer-payment-method type="PAYMENT_CARD"> in the same layout.
Why this happens: <primer-payment-method type="PAYMENT_CARD"> internally creates its own <primer-card-form>. When you also add a custom card form, you end up with two card forms on the page.
Solution: Choose one approach:
Card inputs not working
Cause: Card input components placed outside<primer-card-form>.
Solution: All card inputs must be descendants of the card form:
Dynamic rendering creates duplicates
Cause: When dynamically rendering payment methods,PAYMENT_CARD is included while also using a custom card form.
Solution: Filter out PAYMENT_CARD when using a custom card form:
React-specific issues
Options not being applied
Cause: Creating new object references on every render forces unnecessary comparisons.The Primer SDK implements deep comparison for the
options property. This means unstable references won’t cause re-initialization, but they still add comparison overhead on every render.Calling methods before ready
Cause: Calling SDK methods before the checkout is initialized. Solution: Wait for theprimer:ready event:
React 18 vs React 19 property assignment
Cause: React 18 converts object props to[object Object] strings for web components.
Solution: Use the appropriate pattern for your React version:
- React 18
- React 19
Styling issues
CSS not applying to components
Cause: Trying to style internal elements with CSS selectors. Shadow DOM prevents external CSS from reaching internal elements. Solution: Use CSS variables instead:Flash of Unstyled Custom Elements
Cause:<primer-checkout> is a custom element. Until the browser has registered it (which happens when loadPrimer() actually runs), it has no shadow DOM and no styling of its own. Any content you’ve slotted into it renders as plain, unstyled HTML in the meantime.
This is only a visible problem when both of these are true:
- You’ve slotted your own content into the
mainslot — for example the custom content during loading pattern, where a<div slot="main">wraps your own markup alongside<primer-main> loadPrimer()runs after the page has already painted — for example inside auseEffect, behind a dynamicimport(), or after other startup work
loadPrimer() runs immediately, the window is too short to notice. It’s the combination that causes a visible flash.
Solution: Add a CSS rule to your page — inline in <head>, not dependent on the SDK’s script having loaded — that targets the element until it’s actually defined:
:not(:defined) is true the instant the browser parses an unrecognized custom element — it needs no JavaScript and doesn’t depend on loadPrimer() having run, so it closes the gap regardless of how late the SDK loads.
The rule above is just one example — display: none on the same selector works too. What matters is targeting :not(:defined), not which specific property you use to hide the content.
Validation vs payment errors
Understanding the difference helps with proper error handling:Debugging tips
Log all Primer events
Verify component registration
Check available payment methods
Getting help
When contacting Primer support, include:- The
diagnosticsIdfrom any error callbacks - Your browser, OS version, and framework version
- Steps to reproduce the issue
See also
SSR guide
Server-side rendering patterns
React integration
React-specific guidance
Events guide
Event handling patterns
Build a custom card form
Card form tutorial