A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1# 0001 — Framework & application stack
2
3- **Status:** Accepted
4- **Date:** 2026-06-08
5- **Scope:** Project-wide (SP0 and all later sub-projects inherit this)
6
7## Context
8
9SkyPress has two workloads with opposite characteristics living in one app:
10
111. **The editor** — a heavy, fundamentally *client-side* React application. It embeds
12 Automattic's `@automattic/isolated-block-editor`, which bundles its own copy of the
13 `@wordpress/*` packages (block-editor, block-library, components, …). This cannot be
14 server-rendered; it boots in the browser.
152. **Public reading pages** — must be *fast and light* (brief §6, §10): edge/SSR-rendered
16 HTML, minimal client JS, never shipping Gutenberg's weight to readers.
17
18A single decision-gating fact constrains the whole stack. Verified directly from the npm
19registry on 2026-06-08:
20
21- `@automattic/isolated-block-editor@2.30.0` (latest; republished 2026-01-29 after being
22 dormant since 2024-03 — so it **is** maintained).
23- It declares **no `peerDependencies`** and pulls React transitively through
24 `@wordpress/element@6.24.0`, which depends on **`react@^18.3` / `react-dom@^18.3`**.
25- Therefore the editor requires **React 18**, and the host app must share a **single**
26 React 18 instance with it (React forbids two copies — hooks break otherwise).
27
28This makes React 19 (the default in Next.js 15 App Router) the wrong default path.
29
30## Options considered
31
32| Option | React story | Public-page story | Free host | Verdict |
33|---|---|---|---|---|
34| **Next.js 15 (App Router)** | Defaults to React 19; running RSC with pinned React 18 is fighting the framework | Good (RSC/edge) | Vercel | ✗ React-19 default fights the editor's React-18 lock; RSC adds friction for a fundamentally client-side editor |
35| **Next.js 14 (Pages/App)** | React 18 | Good | Vercel | △ Viable but a step back onto an older line; heavier than needed for mostly-static reading pages |
36| **Remix / React Router v7** | React 18-friendly | Good (loaders/SSR) | Vercel/CF | △ Solid, but every page carries the React runtime; less ideal for ~0-JS reading pages |
37| **Vite + React 18 SPA only** | Full control of React version | Poor — no native SSR for light reading pages | static + functions | △ Great for the editor, but I'd have to bolt on SSR for §6 |
38| **Astro + React 18 islands** | Pin `react@18.3` explicitly; no framework default to fight | Excellent — Astro's core strength is ~0-JS HTML pages | Vercel **or** Cloudflare adapter | ✅ **Chosen** |
39
40## Decision
41
42**Astro 6 + React 18 (`react@18.3.1`), React used only via islands.** One Astro project:
43
44- `/editor` (and similar authoring routes) host the editor as a single
45 `client:only="react"` island. Gutenberg's bundle loads **only** on that route and is
46 never server-rendered — exactly matching how the editor must boot.
47- Public routes (`/`, `/<handle>`, `/<handle>/<slug>`) are Astro pages rendered to light
48 HTML at build/edge time. They render the stored block tree to HTML with the **same**
49 `@wordpress/blocks` + `@wordpress/block-library` versions the editor bundles
50 (`14.13.0` / `9.24.0`), so reading pages ship no editor JS.
51- Server endpoints (OAuth callbacks, blob proxy, etc.) are Astro endpoints — added per
52 sub-project as needed.
53- React is pinned to `18.3.1` at the workspace root and **deduped** so the app and the
54 editor share one instance. The SP0 spike verifies this empirically (no "invalid hook
55 call" / duplicate-React errors).
56
57Hosting target (Vercel vs Cloudflare) is deferred to SP7; Astro keeps both open via
58`@astrojs/vercel` / `@astrojs/cloudflare`. Leaning Cloudflare for the read-through
59renderer, with the OAuth-client runtime decision (SP1) able to influence it.
60
61## Why
62
63- **React 18 is non-negotiable** for the editor; Astro lets me pin it cleanly instead of
64 fighting a framework's React-19 default.
65- **Bundle isolation comes for free**: Astro ships per-island JS, so the heavy editor
66 bundle is physically confined to authoring routes and reading pages stay light —
67 directly satisfying brief §6/§10 ("keep reading pages fast and light despite
68 Gutenberg's heft") without manual code-splitting gymnastics.
69- **One app, dual deploy**: Astro adapters keep both free-tier hosts on the table (§8).
70- **Vite underneath** gives modern, fast bundling and good control over the awkward
71 `@wordpress/*` packages.
72
73## Consequences & risks
74
75- **Gutenberg-under-Vite bundling is the open risk** (the `@wordpress/*` packages
76 sometimes assume a global `wp` and ship CSS). This is precisely what the SP0 spike
77 de-risks empirically. **Fallback if Astro/Vite can't bundle the editor cleanly:** a
78 two-surface monorepo — a plain Vite React SPA for the editor + Astro for public pages —
79 or Next.js 14. Documented here so the fallback is pre-decided.
80- **Version coupling** to `isolated-block-editor`'s pinned `@wordpress/*` line is a
81 maintenance risk to track (brief §4). Render-path packages are pinned to the same
82 versions to avoid drift.
83- Astro's React integration must tolerate a very large `client:only` island; verified in
84 SP0.
85
86## Verification
87
88Empirically proven by the SP0 spike: editor boots, `onSaveBlocks` yields a block array,
89that array round-trips to HTML via the shared `@wordpress/blocks`, and the production
90build reports the editor JS confined to the authoring route. See
91`docs/specs/sp0-foundations-editor-spike.md`.