Architecture¶
tldrq is a Next.js App Router application backed by Supabase, deployed on Vercel. Server components read data; client components handle interaction and write through a small JSON API. Postgres Row Level Security keeps every user's data their own. A separate Electron wrapper turns the deployed site into a macOS app.
Stack¶
- Next.js 16 with the App Router, React 19 and TypeScript.
- Tailwind CSS 4, through
@tailwindcss/postcss, with a class-based dark variant. - Supabase: Postgres, Auth (GitHub and Google OAuth) and Row Level Security, used
through
@supabase/ssrand@supabase/supabase-js. - Cheerio to parse HTML when fetching bookmark metadata.
- ESLint 9 with
eslint-config-next. - Vercel hosts the app; Supabase hosts the database and auth.
- Electron for the desktop app, in
electron/with its ownpackage.json(see Desktop App Internals).
The exact versions are the ones in package.json:
{
"name": "tldrq",
"version": "0.2.11",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"@supabase/ssr": "^0.12.7",
"@supabase/supabase-js": "^2.98.0",
"cheerio": "^1.2.0",
"next": "16.4.0",
"react": "19.3.0",
"react-dom": "19.3.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^26",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.4.0",
"tailwindcss": "^4",
"typescript": "~6.0.3"
}
}
Directory map¶
src/
app/
(main)/ authenticated layout and the dashboard page
api/ JSON route handlers: bookmarks, collections, metadata, shared-views
auth/ OAuth callbacks and the desktop sign-in hand-off pages
login/ sign-in page
share/[slug]/ public share page
layout.tsx root layout (fonts, theme script)
globals.css Tailwind import and the dark variant
components/
auth/ sign-in buttons
bookmarks/ saving bar, cards, list, filters, search, import, export, refresh
collections/ collection pills and settings menu
layout/ header, sign-out, theme toggle
share-button.tsx share dialog
lib/
supabase/ server, browser and proxy Supabase clients
utils/ metadata, priority, slack, slug, time, ui, fade-collapse
types/database.ts Collection, Bookmark, SharedView
proxy.ts session refresh and sign-in redirect on every request
supabase/ schema.sql, migrations/, drop_all.sql
electron/ macOS desktop wrapper
website/ this documentation site
Routes¶
| Path | Kind | Purpose |
|---|---|---|
/ |
Server page | The dashboard (src/app/(main)/page.tsx) |
/login |
Page | Sign-in buttons |
/share/[slug] |
Server page | A public share link |
/auth/callback |
Route handler | Web OAuth callback |
/auth/callback-desktop |
Route handler | Desktop OAuth callback |
/auth/login-desktop |
Client page | Starts desktop sign-in in the system browser |
/auth/desktop |
Client page | Finishes desktop sign-in inside the app |
/api/bookmarks, /api/bookmarks/[id] |
Route handlers | Bookmark CRUD |
/api/collections, /api/collections/[id] |
Route handlers | Collection CRUD |
/api/metadata |
Route handler | Runs the metadata fetcher for a URL |
/api/shared-views |
Route handler | Creates share links |
The JSON endpoints are documented in the API Reference, and the auth routes in Authentication.
Server and client components¶
Server components fetch data and pass it down as props:
src/app/(main)/layout.tsxis the auth guard: it callssupabase.auth.getUser(), redirects to/loginwhen there's no user, and renders the header.src/app/(main)/page.tsxreadscollection,filter,priority,sortandsearchfromsearchParams, queries thecollectionsandbookmarkstables, and renders the dashboard.src/app/share/[slug]/page.tsxrenders share links;src/app/login/page.tsxthe sign-in page.src/components/bookmarks/bookmark-list.tsxandsrc/components/layout/header.tsxrender on the server too.
Everything interactive is a client component ("use client"): url-input, bookmark-card,
bookmark-card-readonly, bookmark-list-wrapper, bulk-import, import-button, export-button,
delete-all-button, refresh-button, refresh-context, filter-tabs, search-input, collection-tabs,
collection-menu, share-button, login-buttons, sign-out-button and theme-toggle, plus the pages
src/app/auth/desktop/page.tsx and src/app/auth/login-desktop/page.tsx. Frontend maps them all.
Request lifecycle¶
A page load passes through the proxy and the layout's guard before the page queries Supabase. A change made in the
browser goes through an API route, then router.refresh() re-runs the server components so the page shows the new
data.
sequenceDiagram
participant B as Browser
participant P as proxy.ts
participant L as (main)/layout.tsx
participant G as (main)/page.tsx
participant A as /api route handler
participant S as Supabase
B->>P: GET /
P->>S: getUser() refreshes the session cookies
P-->>B: redirect to /login if signed out
P->>L: signed in
L->>S: getUser()
L->>G: render
G->>S: select collections and bookmarks (RLS)
G-->>B: HTML
B->>A: fetch, e.g. PATCH /api/bookmarks/id
A->>S: getUser(), then update (RLS)
A-->>B: JSON
B->>G: router.refresh()
G->>S: select again
G-->>B: updated page
Data access and security¶
Every query runs as the signed-in user: the Supabase clients use the project's public anon key plus the user's
session cookies, so Postgres Row Level Security limits each user to their own rows. Route handlers also filter by
user_id, and anonymous visitors can read only what share links expose. Reads happen in server components; writes
go through the route handlers under src/app/api.
See Database for the tables and policies, and Authentication for sessions and sign-in.
Where to go next¶
- Local Development: run it on your machine.
- Configuration: environment variables and config files.
- Deployment: Supabase, OAuth and Vercel.