Skip to the content
nullroute
Browse the docs

The website is not the device

apps/web builds nullroute.diy. It is a Next.js site, it obviously uses the network, and it is fenced off from the device build the same way packages/bridge is:

  • nothing in apps/web ever ships to the Pi
  • MANIFEST.lock and the manifest root hash cover packages/, spec/, provisioning/, package.json and package-lock.json, which is what make print-manifest-roots prints. This line said packages/ only, so anyone following it computed a root hash matching neither the device nor the release.
  • the no-network lint rule applies to packages/, not to apps/web
  • apps/web must never import from packages/daemon

The site renders docs/ from the repository root, which is deliberate: the published documentation and the documentation in the repository are the same files, so the site cannot drift from the device.

If you find yourself wanting to import device code into the website to show something off, that is the boundary doing its job. Copy the values into docs/ or generate a static artifact instead.

Editing a page changes the CSP

vercel.json carries a strict Content-Security-Policy with no 'unsafe-inline' anywhere, including for styles. Next inlines a small RSC bootstrap into every page, so script-src enumerates the sha256 hash of each of those blocks.

Those hashes are content-derived, which means any edit to any page changes them. After changing the site, run:

make web-build
node tools/gen-csp.mjs     # rewrites vercel.json

make web-csp fails in CI if you forget. That is friction, and it is the price of not writing 'unsafe-inline' and moving on. A policy with 'unsafe-inline' in script-src permits exactly the injection the policy exists to prevent, on the site that tells people to verify things.

This works only because the hashes are stable across builds, which is only true because next.config.mjs pins generateBuildId. Do not remove that pin.


Rendered from CONTRIBUTING.md, the same bytes that ship with the code. SHA-256 80ae67da20b1d79a061ae1ffe5ac6af62ad6b80566961c8d27c3a1b5fb0e265d