{"id":51129641,"url":"https://github.com/poran-dip/frenderer","last_synced_at":"2026-06-25T11:01:05.943Z","repository":{"id":352464837,"uuid":"1215248473","full_name":"poran-dip/frenderer","owner":"poran-dip","description":"Execute client-side JavaScript and extract fully rendered HTML or text — without a browser.","archived":false,"fork":false,"pushed_at":"2026-04-19T18:17:45.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-19T19:20:43.646Z","etag":null,"topics":["crawler","headless-browser","prerender","rendering","seo","spa","ssr"],"latest_commit_sha":null,"homepage":"https://npmjs.com/frenderer","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/poran-dip.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-19T17:12:58.000Z","updated_at":"2026-04-19T18:32:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/poran-dip/frenderer","commit_stats":null,"previous_names":["poran-dip/frender"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/poran-dip/frenderer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poran-dip%2Ffrenderer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poran-dip%2Ffrenderer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poran-dip%2Ffrenderer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poran-dip%2Ffrenderer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/poran-dip","download_url":"https://codeload.github.com/poran-dip/frenderer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/poran-dip%2Ffrenderer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34771664,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crawler","headless-browser","prerender","rendering","seo","spa","ssr"],"created_at":"2026-06-25T11:01:05.089Z","updated_at":"2026-06-25T11:01:05.925Z","avatar_url":"https://github.com/poran-dip.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# frenderer\n\nExecute client-side JavaScript and extract fully rendered HTML or text — without a browser.\n\nPerfect for scraping, SEO, and AI pipelines — use `--text` to extract clean, readable content from modern SPAs.\n\n```bash\nfrenderer https://example.com\n```\n\n## How it works\n\nMost modern sites are JS-rendered — the raw HTML is just an empty `\u003cdiv id=\"root\"\u003e`. frenderer runs the page’s JavaScript in a sandboxed environment, lets the DOM fully resolve, then returns the final result.\n\nPipeline:\n\n**Tokenizer → Parser → DOM → JS Sandbox → Cleaner**\n\nfrenderer exists for one simple reason: most tools either don’t execute JavaScript, or require a full browser to do it. This sits in the middle — fast, lightweight, and purpose-built for content extraction.\n\nAll built from scratch in Node.js. No Puppeteer. No Playwright. No headless browser.\n\n## Install\n\n```bash\nnpm install -g frenderer\n```\n\nRequires Node.js 22+.\n\n## CLI\n\n```bash\nfrenderer \u003curl\u003e [options]\n```\n\n| Flag                    | Default | Description                                                |\n| ----------------------- | ------- | ---------------------------------------------------------- |\n| `-o, --out \u003cfile\u003e`      | stdout  | Write output to file                                       |\n| `-s, --settle \u003cms\u003e`     | 1000    | Wait after scripts run for async renders                   |\n| `-t, --timeout \u003cms\u003e`    | 30000   | Total render timeout                                       |\n| `--no-js`               | —       | Skip JS execution, static fetch only                       |\n| `--script-timeout \u003cms\u003e` | 5000    | Per-script execution kill timeout                          |\n| `--attributes`          | —       | Preserve all HTML attributes (disable attribute stripping) |\n| `--text`                | —       | Extract plain text only, no HTML tags                      |\n| `--pretty`              | —       | Pretty-print output                                        |\n| `--no-clean`            | —       | Raw rendered HTML, no cleaning                             |\n| `-H, --header \u003ck:v\u003e`    | —       | Add request header (repeatable)                            |\n| `-v, --verbose`         | —       | Show script errors and timing per script                   |\n| `-q, --quiet`           | —       | Suppress progress output                                   |\n\n### Examples\n\n```bash\n# Render a JS-heavy SPA\nfrenderer https://myapp.vercel.app\n\n# Save to file\nfrenderer https://myapp.vercel.app -o out.html\n\n# Extract plain text (great for LLM input or search indexing)\nfrenderer https://myapp.vercel.app --text -o content.txt\n\n# Give async frameworks more time to render\nfrenderer https://myapp.vercel.app -s 3000\n\n# Static fetch only, no JS\nfrenderer https://example.com --no-js\n\n# Keep raw HTML with all attributes intact\nfrenderer https://myapp.vercel.app --no-clean --attributes\n\n# Pass cookies for auth-protected pages\nfrenderer https://myapp.vercel.app -H \"Cookie: session=abc123\"\n\n# Debug script errors\nfrenderer https://myapp.vercel.app --verbose\n```\n\n## Library\n\n```js\nimport { frenderer } from \"frenderer\";\n\nconst html = await frenderer(\"https://myapp.vercel.app\", {\n  settle: 2000, // ms to wait for async rendering\n  timeout: 30000, // total timeout\n  js: true, // execute scripts\n  clean: true, // strip noise (default)\n  headers: {}, // custom request headers\n});\n```\n\n### Clean options\n\nBy default frenderer strips classes, aria labels, data attributes, scripts, styles, SVGs, comments, and Next.js image optimizer URLs — leaving lean, readable HTML.\n\n```js\nconst html = await frenderer(url, {\n  clean: {\n    class: true, // Tailwind / CSS classnames\n    aria: true, // aria-* attributes\n    data: true, // data-* attributes\n    style: false, // inline styles\n    id: false, // id attributes\n    scripts: true, // \u003cscript\u003e blocks\n    styles: true, // \u003cstyle\u003e blocks + \u003clink rel=stylesheet\u003e\n    svg: true, // \u003csvg\u003e blocks (icons)\n    comments: true, // \u003c!-- comments --\u003e\n    srcset: true, // srcset attributes\n    imgattrs: true, // width/height/loading/decoding\n    imgurls: true, // decode Next.js image optimizer URLs\n    decimals: true, // trim long decimals in inline styles\n    attributes: true, // strip ALL attributes (overrides above)\n    textonly: false, // extract plain text only\n    whitespace: true, // collapse blank lines\n  },\n});\n```\n\n### As a prerender middleware\n\nDrop-in SEO for existing Vite/CRA apps — serve rendered HTML to crawlers, normal SPA to users:\n\n```js\nimport { frenderer } from \"frenderer\";\n\nconst BOT_UA = /googlebot|bingbot|twitterbot|facebookexternalhit|crawler/i;\n\napp.use(async (req, res, next) =\u003e {\n  if (!BOT_UA.test(req.headers[\"user-agent\"] || \"\")) return next();\n\n  const html = await frenderer(`http://localhost:${PORT}${req.url}`, {\n    settle: 1000,\n  });\n  res.send(html);\n});\n```\n\n## Security\n\nScript execution runs inside two layers of isolation:\n\n1. **`vm` context** — sandboxed V8 isolate, no access to host globals (`require`, `process`, `__dirname`, env vars)\n2. **Worker thread** — each render runs in an isolated thread; even if a script escapes the vm, the main process is unaffected\n\nDesigned to safely execute untrusted page scripts using multiple isolation layers.\n\n## Limitations\n\n- Pages that require authentication (cookies/sessions) need headers passed manually\n- Sites using WebGL, Canvas, or WebAssembly for rendering won't work\n- Heavy anti-bot detection (Cloudflare, Akamai) may block requests\n- Not a replacement for proper SSR frameworks (Next.js, Nuxt) in new projects\n\n## Contributing\n\nfrenderer is early and intentionally simple. If something feels missing or could be cleaner, you're probably right.\n\nAreas that could use help:\n\n- Better JS compatibility (framework edge cases)\n- Smarter DOM → text extraction\n- Performance improvements\n- Plugin/hooks system\n\nPRs and ideas welcome.\n\n## License\n\nApache 2.0\n\n\u003e Turn modern JS-heavy websites into clean, readable content — no browser required.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fporan-dip%2Ffrenderer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fporan-dip%2Ffrenderer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fporan-dip%2Ffrenderer/lists"}