Docs

Third-party integrations

Add consent-aware Google Analytics, deferred scripts, and click-to-load YouTube embeds to Proa pages.

Open Markdown

Optional third-party components for Proa: deferred external scripts, Google Analytics 4, and click-to-load YouTube embeds. Components render through WebRenderSync<L, B> and support custom writers. They work in plain SSR apps without an RSJS runtime. Unrendered integrations emit no browser code.

The browser adapters here are the boundary to external SDKs and iframes; application state and interactive application components can continue using RSJS.

Install

Until this crate is published on crates.io, use the workspace's Git source consistently for your Proa dependencies:

proa_third_party = { git = "https://github.com/proa-labs/proa", default-features = false, features = ["google-analytics", "youtube"] }

The default features include both providers. Disable defaults and select just the providers your application uses. Script is available without features.

Google Analytics

use proa_third_party::google::GoogleAnalytics;

let analytics = GoogleAnalytics::new("G-XXXXXXXXXX")?;
// Compose {analytics} into your shared html_sync! document layout.

In Markdown, GoogleAnalytics implements both MdRenderSync and MdRender as a no-op. Composing it into md_sync! or md! emits no text, scripts, or whitespace.

The default is consent required: no Google SDK request and no retained tracking events until your application's consent UI grants permission. After the component has rendered, connect the UI to the browser API:

window.proaThirdParty.googleAnalytics.setConsent(true);
// Later, if the visitor withdraws permission:
window.proaThirdParty.googleAnalytics.setConsent(false);

The decision applies to all GA measurement IDs rendered in this document. Applications own consent collection, persistence, and any cookie cleanup. A revocation disables subsequent tracking and cancels SDK downloads that have not started; it cannot retract data already sent or unload an existing SDK. This integration implements a basic loading gate, not a consent-management UI.

If the application has already established permission to collect, render GoogleAnalytics::new(id)?.consent(AnalyticsConsent::Granted) instead, importing AnalyticsConsent from proa_third_party::google. Setting Granted does not obtain consent from the visitor. Google Signals and advertising personalization signals remain disabled; advertising consent states are denied.

The default script strategy waits for window.load. Use .strategy(ScriptStrategy::Async) to load earlier or ScriptStrategy::Idle to wait for idle time after load. Longer delays can miss short visits.

Track events after permission is granted:

window.proaThirdParty.googleAnalytics.track("G-XXXXXXXXXX", "sign_up", {
  method: "email",
});

Events queue while the SDK downloads. The method returns false if that measurement ID is unregistered or disabled. Do not send personal information. An existing window.dataLayer or window.gtag is preserved. Multiple instances share one SDK; each measurement ID is configured once per document. A repeated ID uses its first declaration's settings.

By default GA sends an automatic document page view. Use .send_page_view(false) if you send page views yourself, and configure the GA web stream's enhanced measurement settings accordingly to avoid duplicates. This crate does not add a second client-router page-view listener.

Environment variables and production-host checks belong to your application. The component honors window["ga-disable-" + measurementId] === true when it starts, so applications can set the standard GA opt-out flag before rendering the bootstrap. No API secret, OAuth token, or service-account key is needed.

YouTube

use proa_third_party::youtube::YouTubeEmbed;

let video = YouTubeEmbed::new("M7lc1UVf-VE", "YouTube player demonstration")?
    .start_at(10)
    .class("my-video");
// Compose {video} into html_sync! or render it through MdRenderSync.

HTML initially contains a lazy thumbnail, a keyboard-operable Play button, and a persistent link to the video. No player iframe or YouTube player SDK loads until activation. The iframe uses youtube-nocookie.com, preserves a referrer for player compatibility, and receives focus when inserted. Autoplay is requested only after activation and remains subject to browser policy.

The default thumbnail comes from i.ytimg.com, which can receive a request before playback. Use .poster("/images/my-video.webp")? for a locally hosted poster and no YouTube request before activation. A privacy-enhanced iframe is still a third-party connection once the visitor plays the video.

The supplied CSS reserves a 16:9 frame with a minimum height of 200px. It does not require Tailwind. Styles are inline by default; use .inline_styles(false) and serve proa_third_party::youtube::YOUTUBE_CSS as an external text/css asset when your CSP disallows inline styles or you want one cached stylesheet. Include that stylesheet before the embed to preserve its layout dimensions.

For fully custom styling, combine .class("my-video").inline_styles(false) and style .my-video in your app. The internal data-proa-youtube-frame, data-proa-youtube-play, and data-proa-youtube-play-icon selectors identify the frame and controls. Your styles should reserve the player's dimensions before activation. Using YOUTUBE_CSS is optional.

Both MdRenderSync and MdRender produce an escaped title and a normal YouTube link, with no HTML, scripts, or iframe. The start offset applies to embedded playback; the fallback and Markdown links point to the video itself.

Other scripts

use proa_third_party::{Script, ScriptStrategy};

let widget = Script::new("https://cdn.example.com/widget.js")?
    .strategy(ScriptStrategy::Idle);

Only use script URLs whose code you trust. HTTPS and root-relative URLs are accepted. .integrity("sha384-...") enables Subresource Integrity and anonymous CORS. The loader deduplicates resolved URLs within this API; it does not adopt unrelated script tags inserted by other libraries. The first declaration's options win. Failed requests dispatch proa:script-error; calling window.proaThirdParty.loadScript(url, options) again explicitly retries them. The returned promise resolves to the loaded script. There is no automatic retry loop. Do not initialize GA both here and through a separate tag manager.

CSP

Install the response nonce on WebContext. Components copy it to their inline bootstrap and any scripts they create. Use a matching nonce-based script-src policy; strict-dynamic permits scripts subsequently loaded by the trusted SDK. Configure connect-src/img-src for the GA destinations you enable. YouTube needs frame-src https://www.youtube-nocookie.com and, for default posters, img-src https://i.ytimg.com.

Inline YouTube styles carry the same nonce, which must also be permitted by style-src. Alternatively serve YOUTUBE_CSS from your own allowed origin. The crate never relaxes your response's security headers.

Search

Type at least 2 characters