Viewer
Blueprint inspection GUI at fpsr.fprints.xyz.
The viewer is a React app for pasting, inspecting, previewing, and exporting Factorio blueprint strings. It consumes the workspace fpsr package and private CDN assets.
Live demo: https://fpsr.fprints.xyz
Local development from the monorepo root:
pnpm dev
# or: vp dev apps/viewer
Docs for the library live under Guide; this site is fpsr-docs.fprints.xyz.
Load from URL (?source=)
Open the viewer with a source query parameter pointing at an HTTPS URL that returns a Factorio blueprint string (text/plain). The viewer fetches it through a same-origin proxy, adds it under Custom, selects it, and strips source from the address bar so a refresh does not re-import.
https://fpsr.fprints.xyz/?source=https://www.factorio.school/api/blueprintData/<id>/
Rules:
- HTTPS only (no credentials in the URL)
- Allowlisted hosts:
www.factorio.school,factorio.school - Upstream body capped at 4 MiB; request times out after 15s
The proxy is GET /api/fetch-blueprint?url=… (Cloudflare Pages Function in production; Vite middleware in local pnpm dev). It is not an open proxy — non-allowlisted hosts are rejected.
For large blueprint strings in third-party pages, prefer Embed with postMessage instead of putting the string in the query.
Embed
Third-party sites can iframe a chrome-stripped preview and pass a blueprint string with postMessage (do not put large blueprint strings in the URL).
<iframe
id="fpsr"
src="https://fpsr.fprints.xyz/?embed=1"
title="FPSR blueprint preview"
style="width:100%;height:480px;border:0"
></iframe>
const iframe = document.getElementById("fpsr");
window.addEventListener("message", (event) => {
if (event.source !== iframe.contentWindow) return;
const data = event.data;
if (!data || typeof data !== "object") return;
if (data.type === "fpsr:ready") {
iframe.contentWindow.postMessage(
{ type: "fpsr:load", version: 1, blueprint: "0eN..." },
"https://fpsr.fprints.xyz",
);
}
if (data.type === "fpsr:loaded") {
console.log("loaded", data.kind);
}
if (data.type === "fpsr:error") {
console.error(data.message);
}
});
| Direction | Type | Payload |
|---|---|---|
| iframe → parent | fpsr:ready |
{ version: 1 } |
| parent → iframe | fpsr:load |
{ version: 1, blueprint: string } |
| iframe → parent | fpsr:loaded |
{ version: 1, kind } |
| iframe → parent | fpsr:error |
{ version: 1, message: string } |
Game sprites still load from BunnyCDN under the viewer origin. Hashed atlases cache in the browser and at the edge; each iframe still boots its own app shell and render worker (in-memory atlas cache is not shared across iframes).