
Extract Static HTML
FreeCapture self-contained HTML from web apps effortlessly.
Free · Opens the source repo
What Extract Static HTML does
Extract Static HTML is a skill designed for developers who need to create self-contained static HTML files from web applications or React components. This skill inlines CSS and images, making it ideal for capturing specific UI states or sharing static versions of pages. It is particularly useful when preparing assets for upload to Stitch or when a user simply requests to 'save the HTML' or 'mock the view'.
The skill offers two strategies for extraction: Strategy A, which utilizes Puppeteer for the highest fidelity output, and Strategy B, which employs a browser subagent for scenarios requiring user interaction. Users must choose a strategy before proceeding, with Strategy A recommended as the default due to its superior fidelity and ease of use. This flexibility allows developers to adapt the skill to various use cases, whether they are working with local applications or need to interact with dynamic content.
To use the skill, developers must start their application locally and run a snapshot script that captures the rendered DOM. The output is a fully self-contained HTML file that includes inlined CSS and base64-encoded images, ensuring that the static page looks exactly as it does in the browser. The skill supports multiple frameworks, including React, Angular, Vue, and more, making it versatile for different development environments.
Overall, Extract Static HTML is a practical tool for developers looking to streamline the process of generating static HTML files from dynamic web applications, ensuring that all necessary assets are included for accurate representation.
When to use it
Use this skill when you need to create a static HTML snapshot of a web application or React component, especially when inlining CSS and images is necessary.
When not to use it
This skill may not be suitable for applications that require complex user interactions prior to capturing the HTML, as Strategy B is less preferred.
What you can build with it
Creating a Static Snapshot for Sharing
Use this skill to generate a static HTML file of a web application page that can be easily shared with stakeholders.
Preparing Assets for Stitch Upload
Capture the necessary static HTML and assets for uploading to Stitch, ensuring all styles and images are included.
Mocking a UI State for Testing
Quickly create a static representation of a specific UI state to facilitate testing or review without needing the full application.
How to install Extract Static HTML
View source1. Install with the skills CLI
npx skills add google-labs-code/stitch-skills/extract-static-html --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by google-labs-codeExtract Static HTML
Extract a self-contained static HTML file from any web application.
Which Strategy to Use
You MUST ask the user to choose which strategy to use before proceeding. Present the options clearly, recommend Strategy A as the preferred default, and provide a brief pros/cons summary for each option to help them make an informed decision.
| Strategy A (Puppeteer) | Strategy B (Browser Subagent) | |
|---|---|---|
| When | App runs locally, no auth wall | Need to interact with page first (click, fill forms) |
| Fidelity | Highest — computed styles resolved | High — rendered DOM |
| Setup | Zero — no mock needed | Zero — no mock needed |
| Framework | Any | Any |
| Output | Writes to file — no size limit | May truncate in agent context |
[!WARNING] Checkpoint — User Confirmation Required. You MUST ask the user which strategy they prefer before proceeding. Present the comparison table above, recommend Strategy A as the default, and wait for explicit approval. Do NOT make the decision yourself or proceed until the user confirms.
Strategy A: Puppeteer Snapshot (Recommended)
Launches headless Chrome, captures the fully rendered DOM, and produces a self-contained HTML file with all CSS inlined and images as base64. Works with any framework — no MockPage.jsx needed.
Prerequisites
- App running locally (e.g.,
npm run dev) - Node.js with
puppeteeravailable (check:node -e "require('puppeteer')")
Workflow
-
Start the App and note the port.
[!WARNING] Checkpoint — User Confirmation Required. After starting the local server, you MUST pause and ask the user for confirmation before running the snapshot script or launching a browser subagent. Report the URL and port to the user so they can verify the app is running and rendering correctly. Do NOT proceed to the snapshot step until the user confirms.
-
Run the Snapshot Script:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 \ --output .stitch/home.html \ --wait 2000 -
Multiple pages — run once per route:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 --output .stitch/home.html --wait 2000 npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/pricing --output .stitch/pricing.html --wait 2000 npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/dashboard --output .stitch/dashboard.html --wait 2000 --html-class dark -
Clean Up Dev Server: If a local dev server was started specifically for snapshot extraction, make sure to stop the server process or terminate the background task once extraction is completed.
Script Flags
| Flag | Default | Description |
|---|---|---|
--url | (required) | URL to capture |
--output | (required) | Output file path |
--wait | 1000 | Extra wait (ms) after network idle. Increase for lazy-loading apps. |
--viewport | 1280x800 | Viewport size as WIDTHxHEIGHT |
--html-class | — | Class(es) for <html> element (e.g., dark) |
--remove-fixed | false | Remove fixed/sticky elements (cookie banners, chat widgets) |
--full-height | false | Resize viewport to full scroll height |
--title | — | Override page title (set to the route path, e.g. /dashboard or /settings/profile) |
--auth-script | — | Path to a JS/TS module that exports a default async (page) => void function for authentication |
--inline-canvas | false | Convert <canvas> elements (ECharts, Chart.js, D3) to base64 <img> tags |
What It Does Automatically
- Captures all CSSOM rules from
document.styleSheets(preserves dynamic Vite/Tailwind dev styles and CSS-in-JS) - Inlines all
<link rel="stylesheet">→<style>blocks - Converts
<img>srcandsrcset→ base64 data URIs (skips external fonts) - Inlines same-origin and relative icon font files (
@font-face) as base64 data URIs so ligatures never render as ASCII text - Inlines
<source srcset>URLs as base64 - Removes failed/dead
srcsetentries so the browser falls back to the inlinedsrc - Removes
<script>tags, Vite HMR dev style blocks (createHotContext,import.meta.hot), and dev overlays - Resolves relative CSS
url()paths before inlining
Framework Notes
| Framework | Notes |
|---|---|
| React + Vite | Works out of the box. --wait 1000. |
| Next.js | --wait 3000 for SSR hydration. URL: http://localhost:3000. <img srcset> from /_next/image is auto-inlined as base64. |
| Angular (@angular/cli / v17+) | Works out of the box with ng serve (default URL: http://localhost:4200). --wait 2000 for Angular Material / PrimeNG animation hydration and lazy-loaded routes. |
| Vue / Nuxt | Works out of the box. |
| Svelte / SvelteKit | Works out of the box. |
| Storybook | Use story URL: --url http://localhost:6006/?path=/story/... |
| SSR (Webpack) | May need longer --wait. |
Troubleshooting
| Issue | Solution |
|---|---|
| Images missing | Increase --wait |
| Images show as broken after server stops | Verify srcset was inlined — check log for "Inlined N images". If srcset URLs failed, they are auto-removed so src (inlined) is used. |
| Icons display as text / Serif unstyled font | Ensure snapshot.ts captures CSSOM from document.styleSheets (step 0) and same-origin icon fonts (@font-face) are inlined as base64 data URIs. |
Next.js /_next/image not inlined | Ensure the dev server is running when snapshot runs — the script fetches optimized images from the running server. |
| Dark mode not applied | --html-class dark |
| Cookie banner in output | --remove-fixed |
| Page requires login | Use --auth-script ./auth.ts (see Auth-Gated Pages below) |
| Charts/graphs show as blank boxes | Use --inline-canvas to serialize <canvas> to base64 <img> |
Cannot find module 'puppeteer' | npm install -g puppeteer |
Auth-Gated Pages
For apps with login guards (Vue Router beforeEach, React ProtectedRoute, etc.), create a small auth script that runs in the Puppeteer session:
// auth-myapp.ts
import type { Page } from 'puppeteer';
export default async function authenticate(page: Page) {
// Example 1: Fill and submit a login form
await page.type('#username', 'admin');
await page.type('#password', 'password123');
await page.click('#login-button');
await page.waitForNavigation({ waitUntil: 'networkidle2' });
// Example 2: Inject cookies/localStorage directly
// await page.evaluate(() => {
// localStorage.setItem('token', 'mock-jwt-token');
// });
// Example 3: Call the app's own login API via module injection (Vue/Vite)
// await page.evaluate(() => {
// return new Promise((resolve) => {
// const script = document.createElement('script');
// script.type = 'module';
// script.textContent = `
// import { useUserStore } from '/src/store/modules/user.ts';
// import { fetchLogin } from '/src/api/auth.ts';
// const res = await fetchLogin({ userName: 'Admin', password: '123456' });
// useUserStore().setToken(res.token, res.refreshToken);
// window.dispatchEvent(new CustomEvent('auth-done'));
// `;
// document.head.appendChild(script);
// window.addEventListener('auth-done', () => resolve(true), { once: true });
// });
// });
}
Then use it:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \
--url http://localhost:5173/#/dashboard \
--output .stitch/dashboard.html \
--auth-script ./auth-myapp.ts \
--inline-canvas \
--wait 5000
The script navigates to the --url first (which may redirect to login), runs your auth function, then re-navigates to the original --url with the authenticated session.
Strategy B: Browser Subagent Capture
Use when you need to interact with the page (click buttons, fill forms, navigate tabs) before capturing. The browser subagent gives you full control but output may truncate for large pages.
Workflow
-
Start the App locally.
-
Navigate using a browser subagent.
-
Interact as needed (click, scroll, fill forms).
-
Extract DOM:
document.documentElement.outerHTML[!WARNING] Large pages may truncate. To handle this:
- Remove
<style>tags before extraction:document.querySelectorAll('style').forEach(el => el.remove()) - Re-add styles statically (Tailwind CDN link, source CSS)
- Remove
-
Save to file.
Appendix: Static Fallback (MockPage.jsx)
[!NOTE] This method is a last resort for when the app cannot run locally (broken deps, missing backend, auth walls with no bypass). It requires manually flattening React components into a single JSX file. Prefer Strategy A whenever possible.
When to Use
- App can't run locally at all
- Page requires auth with no mock/bypass
- You need a specific UI state that's impossible to reach by navigation (error screens, empty states)
Quick Reference
npx tsx <SKILL_DIR>/scripts/extract_inline_html.ts \
--index-css src/css/App.css \
--extra-css index.html \
--outdir .stitch \
--page src/MockPage.jsx:Page.html:"Page Title"
Key flags: --no-tailwind (non-Tailwind apps), --html-class dark (dark mode), --css-files (extra CSS files).
Auto-detection: Tailwind config is auto-detected. @apply directives automatically use <style type="text/tailwindcss">.
MockPage.jsx Rules
- Include the full layout — header, sidebar, footer (read
App.jsfirst) - Flatten all conditionals — pick one state, remove all ternaries and
&&guards - Hardcode all data — replace
{variable}with concrete values, unroll.map()loops - Preserve logos — use
<img>with local paths (post-process will inline them) - Remove floating elements — cookie banners, chat widgets, feedback buttons
Post-Processing
Inline local images:
npx tsx <SKILL_DIR>/scripts/post_process.ts \
.stitch/Page.html --base-dir <app-directory>
Frequently asked questions about Extract Static HTML
Similar skills
Playwright Component Testing
Test React and Vue components in isolation with Playwright.
Fluent UI Blazor
Integrate Fluent UI components in Blazor applications effortlessly.
Build MCP App
Create interactive UI widgets for MCP servers.
Web Design Reviewer
Identify and fix design issues in websites efficiently.
Markstream Install
Seamlessly integrate Markstream for Markdown rendering.
GSAP & Framer Scroll Animation
Create advanced scroll animations effortlessly.
