Publish

A site ships either as static files from pmx export or as a live server from pmx serve. Both build the same pages through the same access rules.

Choose the target using How PathMX runs, then follow the matching workflow below. Run commands from the Quickstart project; bun run pmx uses its installed CLI without a global launcher.

Static export

  1. Export with the public origin the site will live at.

    bun run pmx export --origin https://example.com
    

    You should see Exported N routes to …/dist. Use --out <path> for a different directory and --base-path /guide to mount below a prefix.

  2. Look at the output.

    dist/
    ├── index.html, index.md      each page and its authored Markdown
    ├── llms.txt, robots.txt, sitemap.xml
    ├── 404.html                 missing-page document
    ├── .pmx/                     hashed styles, scripts, and build marker
    └── .pathmx/manifest.json     canonical route contract for hosts
    

    Upload dist/ to any static host. Pages keep client-side navigation and refresh themselves when a new deployment lands.

  3. Read the diagnostics. Export includes only anonymously readable pages. Server-only behavior that cannot run in the exported site is reported with its Source. Review these diagnostics before publishing. Browser-local Actions need a supported browser host; static export does not make arbitrary server operations available offline.

Preview the files over HTTP before uploading. If Python 3 is installed:

python3 -m http.server 8080 --directory dist

Open http://localhost:8080/ and follow the workshop lesson link. Reload the lesson's deep URL, view its .md alternate, and check the stylesheet and Player. This previews files only; it does not run server Actions. Stop it with Ctrl+C. Your production host must serve the exported routes; see the route manifest when configuring redirects or rewrites.

Follow the Cloudflare guide to publish with the optional adapter.

Missing pages

PathMX shows a themed recovery page when a browser opens a missing page. The requested URL stays in the address bar and the response carries HTTP status 404. To replace its content, add paths/404.page.md:

# This trail ends here

The page may have moved. Let's find another way.

[Back to home](/)

The override uses normal Markdown, theme settings, and layout selection. It must be publicly readable; a private override is ignored. Open /404.page to preview it as an ordinary page. Missing-page responses use browser navigation rather than the Source runtime, so recovery links also work without JavaScript. Reload a missing URL to see edits to its recovery content.

Requests that prefer text/markdown receive the override's Markdown, or the default recovery text when no override exists. Raw Source reads and API errors retain their normal error responses. Recovery responses are marked noindex and are not cached.

The live server and Cloudflare adapter select the recovery page automatically. Directory exports include 404.html; configure other hosts to serve it for missing URLs with status 404, preserving the requested URL. With --base-path /guide, that file is dist/guide/404.html. A file-only preview server may show its own error page until configured to use this file.

The automatic convention currently applies to 404.page.md. Files such as 500.page.md or 503.page.md remain ordinary pages; they do not enable exception handling or maintenance mode.

Live server

  1. Set the production environment in <project>/.env.

    PATHMX_ORIGIN=https://example.com
    PATHMX_HOST=127.0.0.1
    PATHMX_PORT=3003
    

    Command-line --host and --port win, then these variables, then PORT. The default is 0.0.0.0:3000.

  2. Run the server from the project directory under a process supervisor.

    bun run start
    

    It stays in the foreground, watches Markdown and CSS, and shuts down cleanly on SIGINT or SIGTERM.

  3. Probe health.

    curl -I http://127.0.0.1:3003/api/runtime/health
    

    You should see 204 while the Source is valid and 503 after a rejected edit until it is corrected.

Keep .env and .pathmx/ out of Git and in your backup policy. Source under paths/ stays in version control.

Supervise a live process

For Deploy's macOS adapter, prepare one ordinary server checkout from the same network Git repository as your authoring checkout. Install Bun and Python 3 for the service user, clone the project, run bun install --frozen-lockfile, and create a separate live branch with git switch -c live. The committed dependencies must already include the compatible @pathmx/deploy package. Configure .env and verify bun run start plus the health probe above before supervising it. Stop the foreground process before loading the service.

This complete example assumes a macOS user named site, a checkout at /Users/site/sources/course, and Bun at /Users/site/.bun/bin/bun. Replace those paths and the service label with your actual values; plist strings do not expand ~ or shell variables. Create ~/Library/LaunchAgents/net.example.course.plist for that service user:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>net.example.course</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/site/sources/course/node_modules/.bin/pmx</string>
    <string>serve</string>
  </array>
  <key>WorkingDirectory</key><string>/Users/site/sources/course</string>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key><string>/Users/site/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    <key>PATHMX_HOST</key><string>127.0.0.1</string>
    <key>PATHMX_PORT</key><string>3003</string>
  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardOutPath</key><string>/Users/site/Library/Logs/course.log</string>
  <key>StandardErrorPath</key><string>/Users/site/Library/Logs/course-error.log</string>
</dict>
</plist>

Load and verify the service

Run these on the server as the service user, with its GUI login session available:

mkdir -p ~/Library/Logs
plutil -lint ~/Library/LaunchAgents/net.example.course.plist
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/net.example.course.plist
launchctl enable "gui/$(id -u)/net.example.course"
launchctl kickstart "gui/$(id -u)/net.example.course"
launchctl print "gui/$(id -u)/net.example.course"
curl -I http://127.0.0.1:3003/api/runtime/health

The plist check returns OK; the service report should show a running process, and health returns 204. Inspect the two log files if it stops or health fails. A user LaunchAgent depends on that user's GUI domain; an SSH-only session does not create it. Do not bootstrap a second copy over an already-loaded service. Configure TLS/proxy delivery separately before exposing the public origin.

These are server-changing bootstrap commands. Deploy setup subsequently discovers this existing service; it does not create it.

For systemd, containers, or managed hosts, configure the equivalent executable and working directory. The process runs in the foreground and closes its live streams and plugin resources on shutdown. The supervisor owns restart policy; the host owns TLS, proxies, logs, and backups.

The project manifest anchors environment, plugins, Actions, and db/. Selecting another Source root does not relocate those resources. Use a loopback binding behind a colocated proxy or tunnel. Keep secrets and durable runtime state in the backup policy, with appropriate filesystem access.

Update a writable repository

For an existing macOS launchd service, Server deployment coordinates shared-Git updates, backups, validation, restart, and recovery reports. Use its setup and dry-run before the first managed update.

A live site that accepts Source mutations needs one stable checkout. Checkpoint and push domain Source changes, back up .env and durable db/, then fetch and merge or fast-forward the new revision. Install locked dependencies, run bun run check, restart the supervised process, and check health and representative application behavior.

Rollback with a corrective or revert commit merged into the live history so later domain changes survive. Immutable release-directory switching fits read-only Sources; it does not preserve a writable repository's continuity by itself.

Next

Back to the docs home, or continue with Authoring.