Dev.to WebDev πŸ›  Dev πŸ‘ 0 πŸ“– 3 min read

The one-line way to add live chat to a hosted storefront or a custom-built site

One <script> tag, placed in the single layout file every page renders through, loaded async, with a guard so client-side navigation doesn't load it twice. That's the whole answer. The rest of this post is the five place

One <script> tag, placed in the single layout file every page renders through, loaded async, with a guard so client-side navigation doesn't load it twice. That's the whole answer.

The rest of this post is the five places people get it wrong.

The line

<script async src="EMBED_SRC" data-site="YOUR_SITE_KEY"></script>

Two values, both issued for your site: EMBED_SRC and YOUR_SITE_KEY. Copy them from the snippet you were given β€” don't retype them from a screenshot, and don't hand-edit the source URL.

Put it immediately before </body>. Not in <head>: async helps, but there's no reason for the widget to compete with your largest contentful paint for bandwidth.

Where it goes β€” once

Static site

The footer partial β€” whatever your build includes on every page. One file, one paste.

Single-page app

The root layout: the _app file, App.vue, layout.tsx, the component that wraps every route. Not the router outlet, and not individual pages.

Hosted storefront

The theme's main layout file β€” the single file every page renders through. One paste covers product pages, collections, cart, and checkout.

If you find yourself pasting it into three templates, stop. You now have three places to update the site key.

Guard against the double load

Frameworks remount layouts on some navigations. Check before injecting:

<script>
  (function () {
    if (window.__chatEmbedLoaded) return;
    window.__chatEmbedLoaded = true;
    var s = document.createElement('script');
    s.async = true;
    s.src = 'EMBED_SRC';
    s.setAttribute('data-site', 'YOUR_SITE_KEY');
    document.body.appendChild(s);
  })();
</script>

Same two values, one flag. Verify it below.

Give the widget something to work with

A one-line embed can't guess what the visitor is asking about. Whatever the embed accepts as page context β€” the current URL, an order reference, a hashed user id β€” set it before the loader runs, and only the keys the snippet documents.

Why this matters more than it sounds: the point of the handoff. Rules answer the questions you've already written answers for, AI covers the long tail in between, and a human steps into the same conversation when both run out. If the human arrives without the page URL and the order number, their first message is asking for them.

The shop side of that handoff is publicly demoable without a signup, so you can see what the agent's console actually looks like mid-conversation before you install anything. And the client notifies you even when it's in the background, so "someone is waiting" doesn't depend on you keeping a tab in front of you.

Add the embed origin to your CSP

If you ship a Content-Security-Policy, the embed is a new origin and it will be blocked silently. Take the scheme and host of EMBED_SRC, strip the path, and call that EMBED_ORIGIN:

Content-Security-Policy:
  script-src 'self' EMBED_ORIGIN;
  connect-src 'self' EMBED_ORIGIN;
  frame-src EMBED_ORIGIN;

Do this before you paste, not after you've spent an afternoon on a blank corner of the screen.

Verify in sixty seconds

  1. DevTools β†’ Network, filter by EMBED_ORIGIN: the request appears on the home page.
  2. Navigate to five different pages: still exactly one request per load.
  3. Console: no CSP violation, no 404 on the source.
  4. Resize to a mobile viewport: the launcher is visible and doesn't cover your add-to-cart or form submit button.
  5. Trigger a soft navigation (client-side route change): still exactly one request.

If step 2 or 5 shows two, the guard above is missing or the tag is in two templates.

Why one line is the right unit

The embed is the install, not the product. Rules, AI answers, and the human handoff live in the conversation layer, not in the snippet β€” which means you can ship the widget today and add rules next week once you've seen what people actually ask. That split is also why the install stays one line when the logic grows.

The same install is documented here β€” aumcreate.com/aumchat. If the site happens to be WordPress, there's an installer plugin and you paste nothing at all.

Pitfalls, in order of frequency

  • Inside a conditional block. The tag ends up only on the pages where that condition held. Put it in the layout, unconditionally.
  • In <head> with no async. Blocks parsing. If you must keep it in the head, keep async and move it down when you can.
  • Re-injected on every route change. No flag, no guard, three widgets stacked in the corner.
  • CSP added later. Everything worked in staging because staging had no policy.
  • Site key hardcoded in three templates. You'll find out when you rotate it.
πŸ“° Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.