Upload one HTML file, get an unlisted share page, and let others view it in a sandboxed preview.
- Live app: https://sharehtml.zhenjia.dev
- GitHub repo: https://github.com/lifeodyssey/share-html
- Workers fallback: https://share-html.zhenjiazhou0127.workers.dev
- Product and system spec: docs/project/spec.md
- Current progress: docs/project/progress.md
- Roadmap: docs/project/roadmap.md
Share HTML is a small Cloudflare-hosted tool for sharing self-contained HTML files. It is useful for quick prototypes, mockups, receipts, tiny demos, and one-off pages that need a URL without setting up a site.
- Anonymous upload flow with a 365-day expiry.
- Supabase magic-link sign-in for keeping and deleting shares, with auth email delivery handled by Cloudflare Email Service.
- Claim token flow for attaching an anonymous upload to an account later.
- Public share page with status, risk score, and embedded preview.
- Direct preview URL for opening the uploaded HTML by itself.
- Lightweight scanner for suspicious HTML patterns.
- Report API and admin moderation endpoints.
Share HTML intentionally exposes two different URLs after upload:
- Share URL opens the public wrapper page at
/s/:slug. This is the best link to send to someone because it includes the title, status, safety context, report action, and embedded preview. - Preview URL opens the sandboxed HTML render at
/v/:slug/. This is useful when you only want to inspect the uploaded page itself. - Claim token is private. Use it with the share ID after signing in if you want to move an anonymous upload into your account.
Share HTML is built to be discoverable and usable by AI agents, not just humans:
llms.txt— AI-readable site guide (also served from/when the request sendsAccept: text/markdown).openapi.json— full API description; the homepage HTML also embeds static content + JSON-LD so non-JS agents can read what the site is and how to call it./mcp— MCP (JSON-RPC) endpoint exposingdescribe_share_html,get_public_share, andcreate_share. The page exposes the same tools in-browser via WebMCP (navigator.modelContext).create_sharelets an agent upload an HTML document and get a shareable URL. It runs through the same anonymous rate limit and risk scanner as the web upload — there is no bypass path.- Discovery files:
robots.txt(with explicit AI-bot rules),sitemap.xml,auth.md, and/.well-known/resources (api-catalog,mcp/server-card.json,webmcp.json,agent-skills,agent-card.json, OAuth/OIDC metadata,security.txt). Unknown/.well-known/paths return404rather than the SPA shell.
- Cloudflare Workers Static Assets for the app shell and API.
- Cloudflare R2 for uploaded HTML objects.
- Cloudflare Email Service for branded auth email delivery.
- Supabase Auth for magic-link users.
- Supabase Postgres for metadata, reports, events, and moderation state.
- React, Vite, and TypeScript for the frontend.
npm install
cp .env.example .dev.vars
npm run build
npm run devSet these Cloudflare Worker secrets before production deploy:
printf '%s' '<legacy-anon-key>' | npx wrangler secret put SUPABASE_REST_KEY
printf '%s' '<worker-api-secret>' | npx wrangler secret put WORKER_API_SECRET
printf '%s' '<random-salt>' | npx wrangler secret put IP_HASH_SALT
printf '%s' 'v1,whsec_...' | npx wrangler secret put SUPABASE_SEND_EMAIL_HOOK_SECRETCreate the R2 bucket once:
npx wrangler r2 bucket create share-html-prodApply the Supabase migration in supabase/migrations/0001_share_html_schema.sql.
For production magic links, add https://sharehtml.zhenjia.dev to the Supabase Auth site URL and redirect allow list.
The Cloudflare Email Service hook and sharehtml@zhenjia.dev sender setup notes live in docs/email/auth-email-setup.md.
Production runs as a Cloudflare Worker named share-html. The deployment source of truth is wrangler.jsonc: it defines the Worker entrypoint, static assets, custom domain, R2 bucket binding, and public runtime variables.
Deployments are gated by GitHub branch protection and executed by Cloudflare Workers Builds:
- Pull requests run
npm ci,npm run build, andnpx wrangler deploy --dry-run. - The
mainbranch requires theBuildcheck before changes can land. - Cloudflare Workers Builds watches
main, builds that branch, and deploys the Worker after merge.
This keeps Cloudflare deploy credentials out of GitHub. No Cloudflare API token or deploy hook secret is required in GitHub Actions.
Cloudflare Workers Builds setup notes live in docs/deploy/cloudflare-builds.md.
You can still deploy manually from a local machine with:
npm run deployThe Worker is configured with:
- Custom domain:
sharehtml.zhenjia.dev - Workers fallback:
share-html.zhenjiazhou0127.workers.dev - R2 bucket:
share-html-prod
Uploaded HTML is not sanitized. It is isolated instead:
- Preview pages are streamed through a dedicated
/v/:slug/route. - Share pages embed previews inside an iframe sandbox.
- R2 objects are private and only read by the Worker.
- Restrictive response headers are applied to preview responses.
- Moderation state is checked before streaming uploaded content.
- Supabase RLS requires a private Worker secret header for server-side metadata writes.