Skip to content

terms-sh/remix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Remix Demo

A Remix 3 application with end-to-end privacy plumbing: a PolicyStack config drives both an on-screen cookie consent banner and the generated privacy & cookie policy pages.

Routes

Path Owner Description
/ app/controllers/home.tsx Home page.
/auth app/controllers/auth.tsx Placeholder for sign-in / sign-up flows.
/privacy app/controllers/privacy.tsx Server-rendered privacy policy compiled from app/data/openpolicy.ts.
/cookies app/controllers/cookies.tsx Server-rendered cookie policy compiled from the same config.
/assets/* app/router.tsapp/assets.ts Bundles and serves browser modules through the Remix asset server.

Stack

  • Remix 3 (remix@3.0.0-beta.0) — server-first, Web-APIs-everywhere. Components are not React; the runtime uses clientEntry + run for hydration.
  • OpenCookies (@opencookies/core) — framework-agnostic consent store. A small clientEntry wrapper in app/ui/cookie-banner.tsx bridges the store's subscribe() into handle.update(). Categories are derived from the OpenPolicy config so the banner and the policy can't drift apart.
  • OpenPolicy (@openpolicy/sdk, @openpolicy/core) — policy-as-code. app/ui/policy-document.tsx walks the framework-agnostic Document AST (Document → DocumentSection → ContentNode → InlineNode) and emits Remix UI JSX, so both policy pages are server-rendered with no client JS.

Layout

app/
├── assets.ts                  # Asset server config (allow-list, fileMap)
├── assets/entry.ts            # Browser entry; calls run() to hydrate clientEntry components
├── data/openpolicy.ts         # Single source of truth: company info, data, cookies, jurisdictions
├── controllers/               # Flat controllers, one file per route
│   ├── home.tsx
│   ├── auth.tsx
│   ├── privacy.tsx
│   └── cookies.tsx
├── routes.ts                  # Typed URL contract
├── router.ts                  # Wires routes to handlers
├── ui/                        # Shared UI
│   ├── document.tsx           # HTML document shell
│   ├── layout.tsx             # Header / nav / main / cookie banner
│   ├── cookie-banner.tsx      # clientEntry banner backed by OpenCookies
│   └── policy-document.tsx    # AST walker for OpenPolicy Documents
└── utils/render.tsx           # renderToStream + resolveClientEntry + resolveFrame

How the cookie banner hydrates

  1. app/ui/cookie-banner.tsx exports CookieBanner = clientEntry(import.meta.url, fn).
  2. On the server, app/utils/render.tsx maps the file:// entry ID to a public asset URL via assets.getHref(), and emits a <script type="application/json" id="rmx-data"> tag listing the entries to hydrate.
  3. app/assets/entry.ts calls run({ loadModule, resolveFrame }); the runtime dynamic-imports the banner module and runs setup again on the client.
  4. The setup function guards localStorage access with typeof window !== 'undefined', creates an OpenCookies store, subscribes for updates, and registers cleanup against handle.signal.

How the policy pages render

  1. app/data/openpolicy.ts exports a policy value built with defineConfig from @openpolicy/sdk.
  2. Each controller calls expandOpenPolicyConfig(policy), picks the right PolicyInput (privacy or cookie), and compiles it once at module load with compile(input) from @openpolicy/core.
  3. The resulting Document is passed to <PolicyDocument doc={doc} />, which walks the AST and emits standard HTML (<section>/<h2>/<p>/<ul>/<table>/<a>).
  4. Pages are fully server-rendered. No clientEntry, no hydration, no client JS for the policy content.

Commands

pnpm i
pnpm run start
pnpm test
pnpm run typecheck

The server listens on http://localhost:44100 (override with PORT).

Growing the app

  • Keep route handlers flat in app/controllers/. Promote to a folder with controller.tsx only when a route grows nested routes or multiple actions.
  • Add app/middleware/, public/, or test/ when something actually needs them.
  • Shared UI belongs in app/ui/. Don't introduce app/lib/ or app/components/ — they overlap with app/ui/ and become dumping grounds.
  • When you add a new client-loaded UI module, allow-list it in app/assets.ts (or widen app/ui/**). New data modules go under app/data/**, already allow-listed.

About

Remix demo of OpenPolicy and OpenCookies

Resources

Stars

Watchers

Forks

Contributors