How to Extract Static HTML from Running Web Applications
The stitch::extract-static-html skill captures fully-rendered web pages by launching a headless Chromium browser via Puppeteer, inlining all CSS and images as base64 data-URIs, and removing development-only scripts to produce a self-contained HTML file.
The stitch::extract-static-html skill in the google-labs-code/stitch-skills repository provides a robust solution to extract static HTML from running web applications. Unlike simple "view source" operations, this tool renders JavaScript-heavy frameworks like React, Vue, and Angular, then materializes runtime-generated styles and embeds all external assets directly into the HTML. This produces a portable snapshot that works independently of the original server or build environment.
How the Snapshot Pipeline Works
The core logic resides in plugins/stitch-design/skills/extract-static-html/scripts/snapshot.ts, which orchestrates a multi-step transformation pipeline entirely within a headless browser context.
Browser Launch and Navigation
The process begins with argument parsing via parseArgs() (lines 72-119) and validation through validateOpts() (lines 165-226), which checks for required fields, valid URLs, and writable output directories. The script then launches a sandboxed headless Chromium instance using puppeteer.launch() (lines 271-281).
Navigation to the target URL occurs via page.goto() (lines 295-309), which implements a fallback strategy from networkidle0 to networkidle2 to handle slow-loading applications. For interactive scenarios, the script supports pre-capture clicks—executing page.$(selector).click() and waiting an additional 2 seconds (lines 317-446)—useful for modal dialogs or dynamic content.
CSS Materialization and Inlining
Before asset extraction, the pipeline handles runtime-generated styles. The CSS-in-JS materialization step (lines 842-876) populates empty <style> tags by extracting rules from the CSSOM, ensuring styled-components and emotion-based styles survive serialization.
External stylesheets undergo stylesheet inlining (lines 884-944), where each <link rel="stylesheet"> is fetched, relative url() references are resolved against the base URL, and the link is replaced with an inline <style> tag containing the resolved CSS.
Asset Inlining and Base64 Conversion
The __snapshot helper library injected into the page context provides toDataUri(), which fetches resources and converts them to base64 data-URIs. This process handles:
- Standard
<img>tags andsrcsetattributes <source srcset>elements in<picture>tags- Background images from inline styles
- SVG
<image>elements - Video poster frames and favicons
Concurrency is controlled via processInBatches() to prevent overwhelming the browser. Additionally, the script parses CSS with extractCssUrls()—a character-by-character parser rather than regex—to inline url() references (lines 1064-1120), optionally including fonts when --inline-fonts is specified.
Cleanup and Output Generation
The final processing phase removes development artifacts. The script strips all <script> tags and known development overlays (Vite, Next.js, CRA) between lines 1246-1294. It also removes fixed-position elements that appear out of view (lines 639-677) and any user-specified selectors (lines 679-694).
The serialized DOM is extracted via document.documentElement.outerHTML (lines 1296-1308) and written to the --output path. Optional JSON reporting provides asset counts, file sizes, and duration metrics for CI/CD integration (lines 1310-1330).
Command-Line Usage Examples
Basic capture of a locally running Vite application:
npx tsx plugins/stitch-design/skills/extract-static-html/scripts/snapshot.ts \
--url http://localhost:5173 \
--output .stitch/home.html \
--wait 2000
Advanced capture with dark mode, viewport specification, and interactive element triggering:
npx tsx plugins/stitch-design/skills/extract-static-html/scripts/snapshot.ts \
--url http://localhost:3000/dashboard \
--output .stitch/dashboard.html \
--html-class dark \
--data-theme dark \
--remove-fixed \
--viewport 1440x900 \
--click "#open-settings" \
--json
Static Fallback and Post-Processing
For scenarios requiring the Static Fallback method using MockPage.jsx, the repository includes post_process.ts to finalize asset inlining after the initial extraction:
npx tsx plugins/stitch-design/skills/extract-static-html/scripts/post_process.ts \
.stitch/Page.html --base-dir path/to/app
The alternative extraction method is implemented in extract_inline_html.ts, which supports the MockPage workflow for specific framework interactions where direct browser manipulation is preferred.
Summary
- Browser-based rendering: Uses Puppeteer to execute JavaScript and render dynamic content before extraction
- Self-contained output: Converts all external assets (CSS, images, fonts) to base64 data-URIs embedded in the final HTML
- Framework agnostic: Works with React, Vue, Svelte, Angular, and plain HTML applications
- Development-ready: Automatically removes dev overlays and scripts while preserving production-essential styling
- Configurable pipeline: Supports viewport customization, element clicking, class injection, and selective removal of fixed elements
Frequently Asked Questions
Does this work with single-page applications (SPAs) using client-side routing?
Yes. The stitch::extract-static-html skill executes all JavaScript during the Puppeteer navigation phase, allowing SPAs to fully hydrate before capture. The networkidle0 to networkidle2 fallback ensures asynchronous routes and lazy-loaded components complete rendering before the snapshot occurs, as implemented in lines 295-309 of snapshot.ts.
How does the tool handle CSS-in-JS libraries like styled-components?
The pipeline specifically materializes CSS-in-JS styles by reading the CSSOM for empty <style> tags and writing the computed rules back into the DOM (lines 842-876 in snapshot.ts). This ensures runtime-generated styles from styled-components, emotion, or similar libraries are preserved in the static output rather than lost during serialization.
Can I exclude specific elements from the final HTML?
Yes. Use the --remove-fixed flag to automatically strip position: fixed and position: sticky elements that appear outside the viewport (lines 639-677). Additionally, pass --remove-selector with any valid CSS selector to eliminate specific elements such as cookie banners or navigation headers (lines 679-694) before the HTML is written to disk.
What is the difference between the main snapshot.ts and the Static Fallback method?
The snapshot.ts script performs live browser rendering via Puppeteer and is the recommended approach for most applications. The Static Fallback method, implemented in extract_inline_html.ts, uses a MockPage.jsx component for specific framework scenarios where direct browser access is limited. The post_process.ts script accompanies this method to finalize asset inlining after the initial JSX-based extraction, ensuring all relative paths are resolved against the correct base directory.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →