|
1 | 1 | # Architecture |
2 | 2 |
|
3 | | -## System Overview |
| 3 | +## Overview |
4 | 4 |
|
5 | | -Memo is a Notion-style workspace app. The stack is a standard Next.js App Router |
6 | | -application backed by Supabase for persistence, auth, and realtime. |
| 5 | +Memo is a Notion-style workspace app: block-based editor, nested pages, workspaces, |
| 6 | +real-time collaboration. Built with Next.js 16 on Vercel, Supabase for data and auth, |
| 7 | +Sentry for error tracking. |
| 8 | + |
| 9 | +## System Diagram |
7 | 10 |
|
8 | 11 | ``` |
9 | | -┌─────────────────────────────────────────────┐ |
10 | | -│ Vercel │ |
11 | | -│ ┌───────────────────────────────────────┐ │ |
12 | | -│ │ Next.js 16 (App Router) │ │ |
13 | | -│ │ ┌─────────┐ ┌──────────┐ ┌──────┐ │ │ |
14 | | -│ │ │ Pages │ │ API │ │ Mid- │ │ │ |
15 | | -│ │ │ (RSC + │ │ Routes │ │ ware │ │ │ |
16 | | -│ │ │ Client) │ │ │ │ │ │ │ |
17 | | -│ │ └────┬─────┘ └────┬─────┘ └──┬───┘ │ │ |
18 | | -│ │ │ │ │ │ │ |
19 | | -│ │ └──────┬───────┘ │ │ │ |
20 | | -│ │ │ │ │ │ |
21 | | -│ │ ┌────────▼────────┐ │ │ │ |
22 | | -│ │ │ Supabase SSR │◄─────────┘ │ │ |
23 | | -│ │ │ (client/server │ │ │ |
24 | | -│ │ │ /proxy) │ │ │ |
25 | | -│ │ └────────┬────────┘ │ │ |
26 | | -│ └──────────────┼────────────────────────┘ │ |
27 | | -└─────────────────┼────────────────────────────┘ |
28 | | - │ |
29 | | - ┌─────────▼─────────┐ |
30 | | - │ Supabase │ |
31 | | - │ ┌─────────────┐ │ |
32 | | - │ │ PostgreSQL │ │ |
33 | | - │ │ (RLS) │ │ |
34 | | - │ ├─────────────┤ │ |
35 | | - │ │ Auth │ │ |
36 | | - │ ├─────────────┤ │ |
37 | | - │ │ Realtime │ │ |
38 | | - │ └─────────────┘ │ |
39 | | - └───────────────────┘ |
| 12 | +Browser |
| 13 | + ├── Server Components → Supabase server client (read data, cookie-based auth) |
| 14 | + ├── Client Components → Supabase browser client (mutations, realtime subscriptions) |
| 15 | + ├── API Routes (/api/*) → server-side logic, health checks |
| 16 | + └── Proxy (src/proxy.ts) → Supabase session refresh, route protection |
| 17 | +
|
| 18 | +Supabase |
| 19 | + ├── PostgreSQL → workspaces, pages, blocks, members |
| 20 | + ├── Auth → GitHub/Google OAuth, email/password |
| 21 | + ├── Realtime → live collaboration (page edits, presence) |
| 22 | + └── RLS → row-level security per workspace |
| 23 | +
|
| 24 | +Sentry → error tracking, source maps, performance monitoring, session replay |
| 25 | +Vercel → hosting, preview deploys per PR, production deploys on merge |
40 | 26 | ``` |
41 | 27 |
|
42 | 28 | ## Data Model |
43 | 29 |
|
44 | | -The core entities follow a workspace → pages → blocks hierarchy: |
| 30 | +``` |
| 31 | +workspace |
| 32 | + ├── has many: members (user_id + role) |
| 33 | + └── has many: pages |
| 34 | + ├── belongs to: workspace |
| 35 | + ├── has one: parent_page (nullable → enables nesting) |
| 36 | + └── has many: blocks (ordered by position) |
| 37 | + ├── type: text | heading_1 | heading_2 | heading_3 | bullet_list | |
| 38 | + │ numbered_list | todo | code | image | divider | callout | toggle |
| 39 | + └── content: JSON (structure varies by type) |
| 40 | +``` |
45 | 41 |
|
46 | | -- **Workspace**: top-level container, owns pages and members |
47 | | -- **Page**: a document within a workspace, supports nesting (parent_id) |
48 | | -- **Block**: content unit within a page, stored as JSON (Tiptap/BlockNote format) |
| 42 | +## Key Technical Decisions |
49 | 43 |
|
50 | | -All tables use Supabase Row Level Security (RLS) to enforce access control. |
| 44 | +| Decision | Choice | Rationale | |
| 45 | +|---|---|---| |
| 46 | +| Editor library | Tiptap or BlockNote (evaluate before building) | Do NOT build a custom editor — this is months of work | |
| 47 | +| Content storage | JSON block tree in PostgreSQL | Flexible, queryable, no HTML parsing needed | |
| 48 | +| Auth | Supabase Auth with RLS | Row-level security at the DB layer, no app-level auth checks needed per query | |
| 49 | +| Realtime | Supabase Realtime subscriptions | Built into the client, no extra infrastructure | |
| 50 | +| Styling | Tailwind v4 + shadcn/ui | No custom CSS, consistent design system | |
| 51 | +| Package manager | pnpm | Strict dependency resolution, faster installs | |
| 52 | +| Session management | Next.js 16 proxy (not middleware) | `src/proxy.ts` with `updateSession` — Next.js 16 convention replacing middleware | |
51 | 53 |
|
52 | | -## Key Decisions |
| 54 | +## Request Flow |
53 | 55 |
|
54 | | -| Decision | Rationale | |
55 | | -|---|---| |
56 | | -| Next.js App Router | Server components by default, streaming, built-in layouts | |
57 | | -| Supabase (not raw Postgres) | Auth, RLS, Realtime, and JS SDK out of the box | |
58 | | -| Tiptap or BlockNote for editor | Block-based editing with slash commands, JSON output | |
59 | | -| JSON block storage | Flexible schema for diverse block types | |
60 | | -| No ORM | Direct Supabase client calls — simpler, fewer abstractions | |
61 | | -| Tailwind + shadcn/ui | Consistent design system without custom CSS | |
| 56 | +1. User visits a page → proxy (`src/proxy.ts`) refreshes Supabase session via `updateSession` |
| 57 | +2. Server component renders with data from Supabase server client (`@/lib/supabase/server`) |
| 58 | +3. Client component hydrates, subscribes to Realtime for live updates |
| 59 | +4. User edits a block → client component writes to Supabase → Realtime broadcasts to other users |
| 60 | +5. Errors captured by Sentry (client via `instrumentation-client.ts`, server via `src/instrumentation.ts`) |
62 | 61 |
|
63 | 62 | ## Component Map |
64 | 63 |
|
65 | 64 | ``` |
66 | 65 | src/ |
67 | 66 | ├── app/ # Next.js App Router |
68 | | -│ ├── layout.tsx # Root layout (fonts, global styles) |
| 67 | +│ ├── layout.tsx # Root layout (Geist fonts, global styles) |
69 | 68 | │ ├── page.tsx # Landing page |
70 | 69 | │ ├── global-error.tsx # Sentry error boundary |
71 | | -│ ├── api/ |
72 | | -│ │ └── health/ # Health check endpoint |
73 | | -│ └── (auth)/ # Auth routes (future) |
74 | | -├── components/ |
75 | | -│ └── ui/ # shadcn/ui primitives |
| 70 | +│ └── api/ |
| 71 | +│ └── health/route.ts # Health check endpoint (DB connectivity) |
76 | 72 | ├── lib/ |
77 | 73 | │ └── supabase/ |
78 | | -│ ├── client.ts # Browser client |
79 | | -│ ├── server.ts # Server component client |
80 | | -│ └── proxy.ts # Session refresh |
81 | | -├── proxy.ts # Root proxy (Supabase session, Next.js 16 convention) |
82 | | -└── instrumentation.ts # Sentry server/edge init |
| 74 | +│ ├── client.ts # Browser client (createBrowserClient) |
| 75 | +│ ├── server.ts # Server component client (createServerClient + cookies) |
| 76 | +│ └── proxy.ts # Session refresh logic (updateSession) |
| 77 | +├── proxy.ts # Root proxy — calls updateSession, skips static/health routes |
| 78 | +└── instrumentation.ts # Sentry server/edge init (register + onRequestError) |
83 | 79 | ``` |
84 | 80 |
|
85 | 81 | ## Observability |
86 | 82 |
|
87 | | -- **Sentry**: error tracking, performance monitoring, session replay |
88 | | -- **Health endpoint**: `/api/health` — checks DB connectivity |
| 83 | +- **Sentry client**: session replay (10% normal, 100% on error), route transition tracking |
| 84 | +- **Sentry server**: PII enabled, local variables, 10% trace sampling in production |
| 85 | +- **Health endpoint**: `GET /api/health` — checks DB connectivity, returns status + latency |
0 commit comments