What Turbulence Optimizes
Turbulence runs each HTML response through a sequence of transformations. Most pages are touched by every step, and most of the work happens automatically — there’s nothing to configure. This page describes what each step does, why it matters, and any cases where Turbulence deliberately leaves something alone.
The transformations run in a specific order. The order matters: each step’s output is the next step’s input. You don’t need to remember the order, but if you’re debugging something specific, knowing which step does what will help.
Pipeline order
Section titled “Pipeline order”Steps run in this order (names match the X-PicPerf-Steps response header):
speculation-rulesresource-hintsself-hosted-font-preloadcritical-cssstatic-prep(slow — runs in the background after the response is sent)imagespreload-syncimage-sizingimage-lazy-loadimage-dimensionsscriptsfonts(slow — runs in the background after the response is sent)
On a cache miss, visitors get the output of the fast (inline) steps immediately. The two slow steps — static-prep and fonts — finish in the background and their result is what gets written to the edge cache. The first visitor may still see un-minified CSS/JS or third-party font <link> tags; subsequent visitors get the fully optimized version from cache. X-PicPerf-Pipeline-Stage: partial on a cache miss means the inline steps finished but the slow steps are still running; full means everything completed before the response was sent (typical on cache hits).
Images
Section titled “Images”Every image on your page gets routed through PicPerf’s image optimization engine. That happens in two steps:
images— rewrites image URLs tohttps://picperf.io/<your-origin-url>.image-sizing— adds a/t/width_N/segment based on the<img>element’s dimensions (see Smart Image Sizing below).
Targets include:
<img src="...">tags<img srcset="...">and<source srcset="...">(candidate-by-candidate)url(...)references inside inline<style>blocks andstyle="..."attributes
url(...) inside external stylesheets are not routed through PicPerf — those files are processed separately by static-prep (minified and self-hosted, with URLs absolutized to your origin).
Once a URL is going through PicPerf, it benefits from everything PicPerf does: modern format conversion (WebP / AVIF), resizing, lossless compression, and aggressive edge caching. The original image at your origin is never modified.
Skipped on purpose: URLs that already point at a known CDN (Cloudinary, Imgix, Shopify CDN, Fastly, BunnyCDN, and a long list of others) are left untouched. If your images are already being served by a serious CDN, Turbulence assumes they’re already optimized.
Smart Image Sizing
Section titled “Smart Image Sizing”After images are rewritten, Turbulence adds a width hint to the URL. This is the same URL-based transformation syntax described in URL-Based Image Transformations — a /t/width_N/ segment inserted into the path. So an image that looked like this:
<img src="https://example.com/hero.jpg">Becomes something like:
<img src="https://picperf.io/t/width_1920/https://example.com/hero.jpg">Turbulence picks the width based on the <img> element itself: if the tag has a width or height attribute, that wins. Otherwise, a default (1024px) is used. Widths are rounded up to the nearest 100px bucket and clamped between 100 and 1920 — that keeps the number of distinct cached images bounded while still giving mobile and desktop good options.
For <img> tags that already have a srcset with a wide range of sizes (e.g., 600w through 1920w), Turbulence may inject a mid-size candidate at 1000w so the browser has a sensible size to pick on mid-sized viewports. Existing candidates are left as-is.
Lazy Loading
Section titled “Lazy Loading”By default, Turbulence adds loading="lazy" to most <img> tags. A few are left alone: the first two images in the document (which might be your LCP, hero, or header logo) and any image with an explicit loading or fetchpriority="high" attribute already set. The browser’s built-in threshold means even lazy-loaded images near the top of the page still load quickly.
decoding="async" is added to every image that’s missing it, so decoding doesn’t block the next paint.
Image Dimensions
Section titled “Image Dimensions”If an <img> is missing width and height attributes, Turbulence probes the image to find its natural size and adds the attributes inline. This eliminates layout shift (CLS) caused by images that paint at unknown dimensions. The probe has a hard time budget, so a page with a hundred images won’t time out — at worst, some images will simply keep their existing (missing) attributes.
Turbulence handles fonts in two complementary steps.
Third-Party Font Self-Hosting (fonts step)
Section titled “Third-Party Font Self-Hosting (fonts step)”If your page loads fonts from Google Fonts (fonts.googleapis.com) or Typekit / Adobe Fonts (use.typekit.net), the fonts step downloads the provider’s CSS, fetches the referenced font files, uploads them to PicPerf’s edge storage (R2), and rewrites the HTML:
- The original
<link rel="stylesheet" href="https://fonts.googleapis.com/...">or<link rel="stylesheet" href="https://use.typekit.net/...">is replaced with<!-- ⚡ INJECTED BY PICPERF ⚡ -->and an inline<style>block whoseurl(...)references point at/pp-static/...on the edge. - Matching
<link rel="preload" as="font" crossorigin>tags are injected so the browser starts downloading the self-hosted files early.
This eliminates third-party font connections and gives you long-term edge caching of the font files themselves.
Note: The fonts step is marked slow, so it runs in the background on the first visit. The first visitor still sees the original provider <link> tags (fonts load from Google or Typekit as usual). Once the step completes, the optimized HTML is written to the edge cache — subsequent visitors get the self-hosted version. Reload the page or wait for cache revalidation to see the injected version.
Bundled Font Preloading (self-hosted-font-preload step)
Section titled “Bundled Font Preloading (self-hosted-font-preload step)”For sites that bundle their own fonts in public/assets/ or similar — a common pattern with Next.js, Astro, and other modern frameworks — the self-hosted-font-preload step scans inline styles, inline style attributes, and same-origin linked stylesheets for .woff2 / .woff / .ttf / .otf references, then injects <link rel="preload" as="font" crossorigin> tags for each. This catches the case where font files live on your origin but nothing tells the browser to fetch them early.
This step runs inline (not in the background), so even the first request gets the preload hints. It intentionally skips Google Fonts and Typekit URLs — those are handled by the fonts step instead.
Scripts
Section titled “Scripts”Turbulence adds async to known-safe third-party scripts. “Known-safe” here means analytics, ad pixels, tag managers, and similar — things that should always load asynchronously and won’t break your page if they execute out of order. The current list covers:
- Analytics: Google Analytics, Google Tag Manager, Mixpanel, Amplitude, Heap, FullStory, Mouseflow, Crazy Egg, Segment
- Ad platforms: Google Ads, DoubleClick, Facebook Pixel, Snapchat, TikTok, Pinterest, LinkedIn
- Chat and support: Intercom, Drift, HubSpot
- A/B testing: Optimizely
- Marketing automation: Marketo, Pardot, Eloqua
- Social widgets: Facebook SDK, Twitter Widgets
If a script is on this list and is currently render-blocking (no async or defer), Turbulence adds async for you. If you’ve already set async or defer, it’s left alone. First-party scripts (served from your own hostname) are never modified.
Scripts that aren’t on the list — including most things served from your own domain — are left exactly as they are. Turbulence doesn’t make guesses about whether an unknown script is safe to async.
CSS and JavaScript
Section titled “CSS and JavaScript”Same-origin <link rel="stylesheet"> and <script src="..."> tags are processed by Turbulence’s static-prep step:
- The asset is downloaded from your origin.
- CSS is minified (unless the URL already looks minified or fingerprinted). JavaScript is minified, with one exception: files using ES module syntax (
import/export) are skipped, because moving them to a different origin breaks their relative imports. - The minified content is hashed and uploaded to PicPerf’s edge storage under a content-addressed filename (e.g.,
main.a3f2b1c9.css). - The HTML is rewritten to reference the new URL at
/pp-static/<hash>.css. - If the CSS is small enough (under ~14 KB), it’s inlined as a
<style>block instead, eliminating the round trip entirely.
The first visitor sees the un-minified, un-self-hosted version. The second-and-later visitors get the optimized version from the edge cache.
Critical CSS
Section titled “Critical CSS”Larger stylesheets that can’t be inlined are split: the rules needed to render above-the-fold content are extracted and inlined as a <style> block, while the full stylesheet is loaded asynchronously. This means the browser can start painting the visible part of the page without waiting for the full CSS file to download.
This is one of the bigger Core Web Vitals wins, and Turbulence does it automatically.
Resource Hints
Section titled “Resource Hints”Turbulence scans your HTML for external origins referenced in src, href, and CSS url(...) attributes, then injects <link rel="preconnect"> and <link rel="dns-prefetch"> tags for them.
- Preconnect opens a full TCP + TLS connection early. It’s expensive (uses a connection slot), so Turbulence reserves it for the most important origins — fonts, image CDNs, and the highest-priority scripts in your
<head>. It’s capped at 4 origins. - DNS prefetch only resolves DNS, which is much cheaper. It catches every other origin the page references.
- The page’s own hostname is excluded (no point preconnecting to yourself).
- Existing hints in your HTML are respected and don’t count toward the cap.
- Font origins get a
crossoriginattribute on their preconnect, since browsers fetch fonts with CORS credentials.
The result: when the browser finally hits a <link rel="stylesheet" href="https://fonts.googleapis.com/...">, the connection is already warm.
Speculation Rules
Section titled “Speculation Rules”For supporting browsers (Chrome, Edge, and others built on Chromium), Turbulence injects a <script type="speculationrules"> block into your HTML. This is a standardized way to tell the browser to prefetch the pages a visitor is likely to navigate to next.
The implementation: Turbulence scans your page for same-origin <a href> links, deduplicates them, and tells the browser to prefetch them with eagerness: "moderate" — meaning the browser prefetches when the user hovers over a link, not on page load. By the time they click, the next page is often already loaded.
Browsers that don’t support Speculation Rules just ignore the <script> tag. It’s a safe progressive enhancement.
Preload Tag Cleanup (preload-sync step)
Section titled “Preload Tag Cleanup (preload-sync step)”If your page has <link rel="preload" as="image" href="..."> tags, the preload-sync step does two things:
- Removes preloads that point at images the page never actually renders. These are common in codebases that have evolved over time — leftover hints from a redesign, or preloads for a different code path. The browser would otherwise fetch them and never use the result.
- Retargets preloads to the optimized URL. If your preload points at your origin’s image URL, but the page actually renders the PicPerf-routed version, Turbulence updates the preload to match. Otherwise, the browser would fetch both — one for the preload, one for the
<img>— and you’d pay for the round trip twice.
What’s Intentionally Left Alone
Section titled “What’s Intentionally Left Alone”A short list of things Turbulence does not do, so you can set expectations:
- Non-HTML responses (APIs, raw JSON, file downloads, video files, etc.) are passed straight through. Turbulence only transforms HTML.
- Cross-origin CSS and JS files are not downloaded, minified, or self-hosted. They pass through as-is.
- URLs in JavaScript-generated content aren’t touched. If your page uses JavaScript to add
<img>elements to the DOM after page load, those URLs aren’t part of Turbulence’s static transformation. (Images added this way are still optimized by PicPerf if you have the regular PicPerf image proxy set up.) - Already-optimized CDN URLs (Cloudinary, Imgix, Shopify, etc.) are left alone.
- Cookies, authentication state, and other request-level behavior are unaffected. Turbulence is a reverse proxy for HTML, not a session-aware layer.