Files
voyage/apps/public-web/ARCHITECTURE.md

290 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
VOYAGE Blog Architecture Documentation
========================================
Purpose
-------
This document describes the architecture of the VOYAGE public blog,
including folder structure, routing, data flow, and rendering logic.
A new developer should be able to understand how blog posts are loaded,
render routes work, and where to extend the system after reading this file.
High-Level Overview
-------------------
The blog is implemented using the **Next.js App Router** with:
- File-based routing
- Dynamic route segments
- MDX-based content
- A shared public layout
Key principles:
- Blog content lives outside the app router (content/posts)
- Routing is derived from folder structure
- Rendering happens in async server components
- Shared UI is colocated in components/
Monorepo Context
----------------
This repository is a monorepo containing multiple applications.
Relevant app for the blog:
- apps/public-web → Public website & blog
Other apps (not covered here):
- workspace-api
- workspace-ui
Folder Structure (Current)
--------------------------
apps/public-web/
├── app/
│ ├── (site)/ → Public site route group
│ │ ├── layout.tsx → Shared layout (TopBar, globals)
│ │ ├── page.tsx → Homepage
│ │ ├── about/
│ │ │ └── page.tsx → Static About page
│ │ ├── blog/
│ │ │ ├── layout.tsx → Blog-specific layout (optional)
│ │ │ ├── page.tsx → Blog index (list of posts)
│ │ │ └── [slug]/
│ │ │ └── page.tsx → Dynamic blog post page
│ │ └── admin/
│ │ └── page.tsx → Admin-only page (protected)
│ │
│ ├── global.css → Global styles
│ └── layout.tsx → Root layout
├── components/
│ └── shell/
│ └── TopBar.tsx → Shared navigation bar
├── visuals/
│ └── ImageSphereSketch.tsx → Creative / visual components
├── content/
│ └── posts/
│ ├── YYYY-MM-DD-title.mdx → Blog post source files
│ └── ...
├── lib/
│ └── posts.ts → Blog data loading & parsing logic
├── public/
│ └── blog/
│ └── visuals/
│ ├── first.jpg
│ ├── second.jpg
│ └── ...
Routing Logic
-------------
Routing is defined entirely by the folder structure.
Static routes:
- / → app/(site)/page.tsx
- /about → app/(site)/about/page.tsx
- /blog → app/(site)/blog/page.tsx
Dynamic routes:
- /blog/[slug] → app/(site)/blog/[slug]/page.tsx
Admin route:
- /admin → app/(site)/admin/page.tsx (protected)
The `[slug]` directory defines a dynamic route parameter
that is passed to the page component as `params.slug`.
Data Flow (How a Blog Post Is Rendered)
---------------------------------------
1. User navigates to:
/blog/some-post-slug
2. Next.js resolves the route:
app/(site)/blog/[slug]/page.tsx
3. The page component receives:
params.slug
4. The page calls:
getPostBySlug(slug) from lib/posts.ts
5. lib/posts.ts:
- Reads the corresponding MDX file from content/posts
- Parses frontmatter metadata (title, date, etc.)
- Returns structured post data
6. The page component renders:
- Shared layout (TopBar)
- Post metadata
- MDX content as React components
7. If the slug does not exist:
- notFound() is triggered
Core Files Explained
-------------------
app/(site)/blog/[slug]/page.tsx
--------------------------------
- Async Server Component
- Receives dynamic params (slug)
- Loads post data via lib/posts
- Handles invalid slugs with notFound()
- Renders full blog post view
app/(site)/blog/page.tsx
------------------------
- Blog index page
- Loads all posts via lib/posts
- Renders list / preview of posts
lib/posts.ts
-------------
- Central data access layer for blog content
- Handles file system access and MDX parsing
- Keeps routing and rendering logic clean
content/posts/*.mdx
-------------------
- Source of truth for blog content
- File name defines the slug
- Frontmatter stores metadata
- Body is rendered as MDX
components/shell/TopBar.tsx
---------------------------
- Shared navigation component
- Used across all public pages
- Ensures consistent layout and navigation
public/blog/visuals/
--------------------
- Static images for blog posts
- Served directly by Next.js
- Referenced in MDX or page components
Admin Authentication & Security
--------------------------------
The blog includes an **admin-only section** used for internal tools
(e.g. editor preview, drafts, future CMS features).
Authentication is **not handled by Next.js**, but delegated to the
existing Spring Boot backend (`workspace-api`).
Key principles:
- Public blog remains fully accessible without login
- Admin routes require a valid backend session
- Session-based authentication (JSESSIONID)
- No JWT, no duplicate auth system
Admin Route Protection (Frontend)
---------------------------------
Route:
- /admin → app/(site)/admin/page.tsx
Protection strategy:
- Implemented as an **async Server Component guard**
- On each request:
- Calls backend endpoint `/api/me`
- Forwards cookies manually
- If response is 401 → redirect to /login
- If response is 200 → render admin UI
Important detail:
- Server-side fetch must forward cookies explicitly
- credentials: "include" is NOT sufficient in Server Components
Login Flow (End-to-End)
----------------------
1. User navigates to:
http://localhost:3000/admin
2. Admin page fetches:
GET http://localhost:8080/api/me
3. If not authenticated:
- Backend returns 401
- Next.js redirects to /login (frontend route)
4. Frontend /login page redirects to backend:
GET http://localhost:8080/login-redirect?redirect=http://localhost:3000/admin
5. Backend:
- Stores redirect target in session
- Redirects to /login (without query params)
6. Spring Security default login page is shown
7. User submits credentials
8. On successful login:
- Custom successHandler reads redirect from session
- User is redirected to:
http://localhost:3000/admin
9. Admin page loads successfully
Backend Endpoints Involved
-------------------------
/api/me
- Returns current authenticated user
- 200 → logged in
- 401 → not authenticated
- Never redirects (API-safe)
/login
- Spring Security default login page
- HTML form-based login
/login-redirect
- Helper endpoint
- Stores redirect target in session
- Avoids unsupported query params on /login
Why This Architecture
---------------------
- Single source of truth for authentication (Spring)
- No duplicate auth logic in frontend
- Clean separation:
- Public content → no auth
- Admin tools → backend session
- Works with SSR and Server Components
- Production-ready pattern for multi-app monorepos
How to Extend (Future)
----------------------
- Admin editor UI
- Draft / preview mode
- Role-based admin features
- CMS integration
- Protected preview links
Current Status
--------------
- App Router fully set up
- Dynamic blog slugs working
- Shared public layout integrated
- MDX content loading stable
- Admin auth flow stable and tested
- Ready for styling, animations, and feature extensions