Skip to content

✨ Move starter to React-free visual editing via @tinacms/astro#34

Open
joshbermanssw wants to merge 17 commits into
mainfrom
jb/move-to-visual-editing
Open

✨ Move starter to React-free visual editing via @tinacms/astro#34
joshbermanssw wants to merge 17 commits into
mainfrom
jb/move-to-visual-editing

Conversation

@joshbermanssw
Copy link
Copy Markdown
Member

@joshbermanssw joshbermanssw commented May 7, 2026

TL;DR

This starter shipped React (@astrojs/react, react, react-dom, react-icons, @tanstack/react-virtual, plus React-based tina/pages/*.tsx editing components) just to satisfy TinaCMS visual editing — exactly the trade-off Astro authors pick Astro to avoid. This PR rips React out of the source tree and rewires visual editing through the new @tinacms/astro + @tinacms/bridge integration from tinacms/tinacms#6771, so editors get the same click-to-focus and live-preview UX with a ~2 kB gzipped bridge instead of a React bundle.

Why

The starter's existing visual editing relied on useTina() — a React hook that re-renders the page tree as the editor types. Cost:

Before After
Source-tree React 5 packages + 4 .tsx files in tina/, 3 in src/components/react/ None
JS shipped to public visitors React + ReactDOM (~140 kB gzipped) on every editable page Bridge bundles only inside the admin iframe (~2.4 kB gzipped); zero on public pages
Custom MDX embeds React component + JSX in tina/pages/*.tsx One .astro renderer + one Template object

PR #6771 ships a vanilla-JS bridge that speaks the existing admin postMessage protocol and triggers per-island refetches via a server endpoint, so click-to-focus / live preview / form sync all keep working without any React in the page tree.

How

  1. Strip React from source. Remove deps + delete every .tsx file under src/ and tina/. Replace the IconLink components with one Astro component using inline SVGs (the icons used are static).
  2. Switch to SSR. output: 'server' + @astrojs/node standalone. Pages now query client.queries.{page,blog} at request time instead of being statically baked from a content-collection loader.
  3. Wire the bridge. BaseHead.astro emits one <script type="application/tina+json"> per form payload and loads @tinacms/astro/bridge with init() + addEventListener('astro:page-load', refreshForms) (so view transitions don't strand the admin on the previous page's form — see PR #6771 commit 370c5f42a).
  4. Per-island endpoints. src/pages/tina-island/[name].ts looks up the island in lib/islands.ts, fetches via withOverlay() (overlay if the bridge supplied one, disk fetch otherwise), and renders the matching component to an HTML fragment with experimental_AstroContainer. Adding an editable region = adding one entry to the registry.
  5. MDX embeds. Schema Template lives next to its renderer (YouTubeEmbed.template.ts / YouTubeEmbed.astro); PageBody registers it on <TinaMarkdown components={…}> by matching name.

Reviewer notes

  • Snapshot pin. Deps point at the unmerged PR's snapshot tag (0.0.0-5f6bcf5-20260506122752). Bump to the proper version once PR #6771 lands.
  • tinacms package itself still pulls React transitively. That's the CMS admin (served at /admin), not the public site. Removing React from tinacms is out of scope here.
  • Catch-all 404s asset paths. [...slug].astro early-returns 404 for slugs containing a . so requests like /favicon.ico don't trigger a getPage() lookup that fills the dev terminal with Unable to find record src/content/page/favicon.ico.mdx GraphQL errors. Page slugs never have dots.
  • tinacms build still needs Tina Cloud env vars (PUBLIC_TINA_CLIENT_ID, TINA_TOKEN) — pre-existing requirement, not introduced here.
  • MDX embed registration is two-sided. The name field on the Template and the key in the components map must match exactly (YouTubeEmbed). Mismatch = embed renders as raw HTML.

Links

@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tina-astro-starter Ready Ready Preview, Comment May 14, 2026 7:07am

Request Review

wicksipedia and others added 2 commits May 11, 2026 16:01
Brings the starter in line with examples/astro/visual-editing:

- astro.config.mjs: add the `tina()` integration (auto-wires the
  edit-mode middleware + /_tina/bridge.js route) and
  vite.ssr.noExternal for @tinacms/astro / @tinacms/bridge
- src/lib/data.ts: load via requestWithMetadata() instead of the
  hand-rolled withOverlay/queries/metadata helpers (now deleted)
- src/lib/islands.ts: IslandRegistry; add global / global-footer
  islands for the header and footer (driven by the `config` collection)
- src/pages/tina-island/[name].ts: experimental_createIslandRoute(islands)
- new src/layouts/Base.astro owns <head> + the global islands;
  BlogPost.astro renders through it
- BaseHead.astro: drop the manual application/tina+json forms and the
  init() bridge script — the integration injects them on edit-mode only
- Header/Footer.astro: take a `config` prop + tinaField markers instead
  of importing config.json statically
- pages use <TinaIsland> instead of data-tina-island divs; PageBody /
  BlogBody import TinaMarkdown from @tinacms/astro/TinaMarkdown.astro and
  use the generated query types
- package.json: Astro 6 -> 5 (matches @tinacms/astro's peer range and the
  reference example); pin @tinacms/astro, tinacms, @tinacms/cli to the
  beta build that includes #6771; add a build:local script
- tsconfig.json: exclude tina/__generated__, tina/collections, tina/config.ts
- README: drop the stale "client:tina directive / requires React" docs

Verified: `pnpm run build:local` passes; `pnpm dev` serves clean
production HTML, injects the bridge + data-tina-form payloads on
?tina-edit=1, and the /tina-island/{page,blog,global,global-footer}
refresh endpoints render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move from output: 'server' to output: 'static' so published content
(home, [...slug] pages, blog index, blog posts, rss) is statically
generated. Only /tina-island/* stays on-demand — visual editing still
works because TinaIsland hydrates client-side in the /admin iframe and
refetches from that endpoint.

- astro.config.mjs: output 'static', redirects /home -> /, swap
  @astrojs/node for @astrojs/vercel (v9 — v10 requires Astro 6)
- data.ts: add listPages() helper
- [...slug].astro / blog/[...slug].astro: add getStaticPaths(), drop
  prerender = false and dead runtime guards
- index.astro / blog/index.astro / rss.xml.js: drop prerender = false

Known noise: @tinacms/astro middleware logs Astro.request.headers
warnings on prerendered pages — upstream issue tinacms/tinacms#6842.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pin tinacms, @tinacms/cli, @tinacms/astro to canary
0.0.0-71cda3f-20260512033339 to test visual editing on statically-built
pages (tinacms/tinacms#6843).

Local astro build: no more Astro.request.headers warnings; prerendered
pages now carry the /_tina/bridge.js bootstrap + data-tina-island
markers. Real verification on the Vercel preview deploy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Matt Wicks <MattWicks@ssw.com.au>
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 12, 2026

CLA assistant check
All committers have signed the CLA.

- Pin tinacms/@tinacms/cli/@tinacms/astro to 0.0.0-afd3b10-20260512095118
- Add `primary` to the page-level <TinaIsland> in index.astro,
  [...slug].astro, and blog/[...slug].astro so the admin opens the
  page's form on load instead of the multi-doc "Referenced Files" list
  (tinacms/tinacms#6843). Global header/footer islands stay non-primary.

astro build: clean, data-tina-island-primary present on prerendered pages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JackDevAU and others added 2 commits May 13, 2026 18:35
- data.ts: mark getPage/getBlog with priority: 'primary' so the admin
  auto-selects the page form on entry (no manual file-tree click).
- Header.astro / Footer.astro: move data-tina-field off the array
  container and onto each nav link + contact icon so clicks open the
  specific list item instead of the empty array root.
- IconLink.astro: accept HTMLAttributes<'a'> + spread, owns nullish
  defaults internally so callers can pass raw config values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All visual editing tests still pass: form auto-loads on page + blog
visual-editor routes, re-navigation works, per-item nav and contact-link
clicks target their specific list items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JackDevAU JackDevAU force-pushed the jb/move-to-visual-editing branch from b2e9556 to a846d96 Compare May 13, 2026 08:35
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants