← Blog · 6 November 2026 · Lire en français

Integrate article audio with no iframe and no third-party script: the server-side block and its contract

If your publishing platform composes pages server-side from vendor blocks and bans iframes, most audio players are ruled out before the first meeting. Here is what our block contract declares, the render budget it is built around, and the two cases it deliberately refuses.

Large newsrooms tend to converge on the same architecture. Pages are not assembled by JavaScript in the reader's browser; a composition platform builds them on the server from blocks, each supplied by a service that has signed up to a written contract. The platform team validates that contract before anything goes live, and its rules are usually blunt: no iframes, no script the team cannot switch off, and a hard timeout per block.

Under those rules, the usual audio player (a snippet that injects an iframe) is not a weak option, it is not an option at all. What the platform team needs is something it can read, validate and render itself. That is what our server-side block is, and the rest of this article is the substance of its contract, so an engineer can assess it without a sales call.

Step one is reading, not integrating

The contract is served at a public address, /api/blocs/contrat, and it needs no API key. That is a design choice: a contract describes how to call us and what our template looks like, nothing in it belongs to a customer, and your validation tooling should be able to check it without asking us for anything. It is cached for five minutes in browsers and an hour at the network edge, because it never changes without a version change.

It declares two blocks, both of type "Display":

  • the player, placed wherever your article template wants it;
  • the synchronised text, which carries the word-by-word highlighting and sits in the article body.

Both read the same data endpoint, so placing both does not double your calls. They do not talk to each other either: the player script looks for the text containers in the page and, if the second block is absent, simply works alone.

Parameters: your article ID, not ours

The data endpoint identifies an article in one of two ways: article, our UUID, or externe plus site, meaning your own article ID (the one you sent when the audio was generated) and your account ID. The second is the one teams actually use, since the template already knows the ID of the article it is composing.

An unusable request does not return an error. It returns an empty but valid block, which your template renders as nothing. We would rather be invisible on a page that asked for nothing than put our incident in your logs.

Templates and styling stay on your side

Templates are declared in Mustache, targeting HTML, in the placeholder position. The markup is written against your own design system's components, which is why the player looks native: it is built from your classes.

We were strict about styling. The player block declares no stylesheet and no font. The text block declares one short stylesheet, every rule prefixed, no hard-coded colour, because word highlighting is the one thing a newsroom design system rarely covers. There is a single script, which your platform concatenates with the others and wraps in an anonymous function, so it leaves no globals behind and boots itself by reading the DOM.

Versions are immutable

The contract carries a version number (currently 1.1.1). Any change to a template, the script or a parameter means a new version, which your team validates again. We do not hot-fix a block in production. That slows our own releases down, and it is the price of what the platform team is buying: what you approved is what runs.

The render budget: 150 milliseconds

Composition platforms do not wait for slow blocks. Past the budget, the block is just not rendered, with no error. Here is what we measured and what changed as a result:

  • the first version, a regular serverless function, took 645 ms on a cold start. The cost was loading a client library, not the database. The route now talks to the database over plain HTTP, in a single read that joins the article, its audio version and the account settings;
  • that brought a cold start to 145 ms: inside the budget with a five-millisecond margin, which is not a margin. The route now runs at the edge, pinned to Paris and then Dublin next to the database, after an unpinned first attempt returned 455 ms from Washington;
  • responses are served from the edge cache, fresh for two minutes and then served stale for up to five more while they refresh in the background. After the very first call for an article, the budget holds;
  • Last-Modified is set and a 304 comes back when you already hold the current version;
  • Server-Timing separates the work of the response from the instance start-up, so your operations team can see which one is slow.

For an editor, the practical consequence is that a pronunciation fix reaches the live page in two to seven minutes, never hours.

The two cases it refuses

Subscriber-only audio. The block is called by your server with no visitor token, so it cannot know who will read the page. It therefore serves exactly what an anonymous visitor would get: the full audio for an open account, the preview otherwise. If your audio is for subscribers only, the right path is the API from your own CMS, where your server vouches for each visitor.

Accounts under embargo. An account that keeps every file behind signed URLs is not served at all. Handing a signed address to a third-party server, for an unknown reader, would be a back door into the very account that asked for none. The block returns nothing rather than a half-measure.

Is this the right path for you?

Probably not if you run a mainstream CMS: the in-page component already avoids iframes with no engineering work, and the WordPress page covers that route. The trade-offs between the approaches are laid out in in-page player versus iframe, and if you want your own player without platform constraints, see using your own player and design system.

The block is for one situation: server-side composition, a written ban on iframes and third-party scripts, and an in-house design system. There, the work is measured in hours: declare the block, place it in a template. The server-side block page summarises it, but the contract itself is the better starting point, and it is public.

Give your articles a voice with WeDispatch

This blog is itself voiced by WeDispatch. Curious how it sounds on your content?

Book a demo

Read next