Deploy a PortalJS portal to PortalJS Arc — Datopian-managed static hosting on Cloudflare. Builds a static export, uploads it, and returns a live SLUG.arc.portaljs.com URL. One command, one target. Use when a portal is ready to publish or redeploy to a live URL.
71
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Publish an existing PortalJS portal to PortalJS Arc — Datopian's managed static
hosting on Cloudflare. Build a static export, upload it to the Arc API, and print a live
https://SLUG.arc.portaljs.com URL. Re-running redeploys the same portal (idempotent on
the slug). This is a single-target skill — it deploys to Arc only. For self-hosting, run
npm run build and upload out/ to any static host; no skill required for that path.
Arc serves static exports only — SSR is not hosted on Arc yet.
package.json that lists next as a dependency.fetch).curl and tar available for packaging and upload.PORTALJS_TOKEN, or ~/.portaljs/credentials
({"token":"…"}). If neither exists, the skill signs in on demand via a device-code
flow; no manual token copying required.git lfs pull before
deploying — large datasets are served from Cloudflare R2 via absolute URLs in
datasets.json, not copied into the export.The canonical, full step-by-step workflow is
.claude/commands/portaljs-deploy.md — the
single source of truth. Read and follow it when executing. Summary:
.) and slug (default from package.json
name or directory name, slugified). Confirm the directory is a Next.js project; reject
reserved slugs (www, api, admin, staging, arc).PORTALJS_TOKEN, else ~/.portaljs/credentials; if
missing, run the device-authorization sign-in flow and save the returned token.next.config.js sets output: 'export' and images: { unoptimized: true },
then run npm run build; stop if the build fails.npm run check-export (or
scripts/check-export.mjs) to catch Git LFS pointer leaks and oversized data files.out/ directory and POST it to $PORTALJS_ARC_API/v1/deploy?slug=<slug>
with the bearer token; handle 200/401/409/400/413 responses distinctly.next.config.js — adds output: 'export' and
images: { unoptimized: true } when absent, preserving the rest of the config.~/.portaljs/credentials (mode 0600).npm run build exits 0, out/index.html exists, the export-hygiene
check passes.https://SLUG.arc.portaljs.com; re-running updates
the same slug in place.| Symptom | Cause | Fix |
|---|---|---|
NOT_A_PORTAL error | No next dependency found in PORTAL_DIR/package.json | Run from a valid portal directory, or pass the correct path. |
| Slug rejected | Derived slug is reserved (www, api, …) or not a valid DNS label | Pass an explicit --slug <name>. |
| Build fails (non-zero exit) | App/config error surfaced in npm run build | Print the log, fix the error, never deploy a failing build. |
check-export fails | Git LFS pointer leaked into out/, or a data file exceeds the size budget | Reference large data by absolute R2 URL via portaljs-add-dataset; don't git lfs pull before building. |
401 on upload | Token invalid, expired, or revoked | Re-run the device sign-in flow once, retry the upload; stop if it 401s again. |
409 on upload | Slug already taken by another account | Choose a different --slug. |
400 / 413 on upload | Malformed slug or export too large | Read the JSON error field and address the specific cause. |
/portaljs-deploy/portaljs-deploy --slug my-open-dataexport PORTALJS_TOKEN=arc_live_xxxxxxxx
/portaljs-deploy ./portals/city-budget --slug city-budget.claude/commands/portaljs-deploy.mdreferences/reference.mdportaljs-new-portal, portaljs-add-dataset, portaljs-connect-ckanebfd391
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.