Skip to main content
Solved

Issue: Klaviyo Embed forms only appearing once per session in Next, Remix, Gatsby etc.

  • October 13, 2023
  • 4 replies
  • 941 views

Forum|alt.badge.img+2

Dear Klaviyo developer team—

I’ve consistently been running into a problem with embed forms in SPA type sites (or even SSR/SSG hybrid type sites) and so have many others:

 

Take this example (Typescript):

import { useEffect } from 'react'

interface KlaviyoFormProps {
listId: string
}

// Declare the _klOnsite property
declare global {
interface Window {
_klOnsite?: any
}
}

export default function KlaviyoForm({ listId }: KlaviyoFormProps): JSX.Element {
useEffect(() => {
window._klOnsite = window._klOnsite || []
window._klOnsite.push(['openForm', listId])
}, [listId])

return (
<div className="" title={`klaviyo-form-${listId}`}>
<div className={`klaviyo-form-${listId}`}></div>
</div>
)
}

When it shows:

  • On first page load/refresh (page with form)
  • Starting on homepage (or other page in app) and navigating to page (within JS router)

When it doesn’t show:

  • After page with form has been viewed, navigating to other pages (within JS router), and returning to page, form no longer shows.

So after digging around the forums for answers, it appears the only workaround is to load this into an iframe instead in order to force some refresh. Bit of a hack...

Question is, how is Klaviyo deciding when to show a form after the trigger? Keeping it hidden after first view is good UX for popup/flyover forms because you want these to stay dismissed, but for embeds you need these to show when ever they are being triggered.

Question: Is this localStorage item preventing embed forms from being re-triggered?

There appears to be a localStorage value saved by klaviyo which is doing this tracking:

 

Proposed solution:

Only track flyover/popup forms in this viewedForms object.

 

 

Best answer by Brian Turcotte

Hi @moreguppy!

My apologies for the delay here - at this time, Klaviyo doesn’t support SPA, so this use case is beyond the scope of Community/Support.

 

That said, I will update the thread if anything changes in the future.

 

Best,

Brian

4 replies

Brian Turcotte
Forum|alt.badge.img+37

Hi @moreguppy!

I’m going to check on this with Engineering for you and I’ll update the thread ASAP!

Best,

Brian


Brian Turcotte
Forum|alt.badge.img+37
  • Klaviyo Alum
  • Answer
  • January 24, 2024

Hi @moreguppy!

My apologies for the delay here - at this time, Klaviyo doesn’t support SPA, so this use case is beyond the scope of Community/Support.

 

That said, I will update the thread if anything changes in the future.

 

Best,

Brian


  • Contributor I
  • March 4, 2026

It’s wild that you don’t support this considering how popular these types of frameworks are.


I was bothered by this exact problem and decided to fix it cleanly. Sharing in case it 
helps others stuck on this.

The crux: Klaviyo's onsite script publishes events internally on a window-level CustomEvent 
channel called onsite-event-publish. One of those events is {type: "PAGE_CHANGE", payload: 
{currentPageUrl: "..."}}, and an internal listener fires Klaviyo's re-render flow whenever the 
pathname in currentPageUrl differs from the previously-stored one.

So: dispatch this event yourself with a unique fake URL whenever the form ends up empty after a 
real navigation, and Klaviyo runs its render path again — this time landing on the 
freshly-mounted form div. The core trick is just:

window.dispatchEvent(
new CustomEvent("onsite-event-publish", {
detail: {
type: "PAGE_CHANGE",
payload: {
currentPageUrl: `${location.origin}/__klaviyo_retry_${attemptNumber}`
}
}
})
);

I packaged it as a drop-in React component (~50 lines, no dependencies):

https://github.com/emilrais/klaviyo-spa-fix

<KlaviyoForm id="YOUR_FORM_ID">
<h4>Subscribe to our newsletter!</h4>
</KlaviyoForm>

Children render only when the form has actually populated (MutationObserver-backed), so on rare 
failure cases you don't end up with an orphaned heading above empty space — it just doesn't 
show.

After a couple thousand production navigations, no visible failures. Effective rate after the 
retry: ~(1/5)^5 ≈ 1/3125. Hope it helps.