Skip to content

Headless CMS: in-place edit, CDN-cacheable pages, hide unpublished posts - #1264

Draft
joepio wants to merge 23 commits into
developfrom
cursor/cms-tutorial-and-publish-55e0
Draft

Headless CMS: in-place edit, CDN-cacheable pages, hide unpublished posts#1264
joepio wants to merge 23 commits into
developfrom
cursor/cms-tutorial-and-publish-55e0

Conversation

@joepio

@joepio joepio commented Aug 14, 2026

Copy link
Copy Markdown
Member

Related Issues

Investigation of the Website-template CMS. No single issue number; closes the tutorial/docs gap and product holes called out in planning/website-templates.md.

Checklist

  • Add changelog entry linking to issue, describe API changes
  • Add or update tests if needed
  • Update docs if needed

What this is

Atomic already has a capable CMS (Website template + Next.js/SvelteKit generators, i18n, forks). This PR fixes the high-impact product holes, then makes generated pages CDN-friendly, then wires in-place WYSIWYG into the Next.js template.

In-place editing (Next.js)

Generated Next.js sites render @tomic/edit-mode (EditModeProvider + Editable). Edit this page / Cmd/Ctrl+E turn the heading and body into contentEditable fields; blur saves to Atomic. The editor signs in with an agent secret stored in localStorage on the site origin — it is never baked into the public bundle. Cmd/Ctrl+Shift+E and Open in Data Browser still open /app/edit?subject=…. SvelteKit keeps the Data Browser footer link (the package UI is React).

CDN / first-byte locale

  • Next.js: generateStaticParams + ISR (revalidate = 60). Catch-all layout owns <html lang> so /nl/... is lang="nl" in the first HTML byte. Ontologies register at store load so SSG includes blocks/menus (instrumentation.ts does not run in generateStaticParams workers).
  • SvelteKit: SSR with Cache-Control: public, s-maxage=60, stale-while-revalidate=86400 (prerendered files are served by sirv and would drop hook headers). %lang% is filled from the URL on the first byte. Blog posts are awaited during SSR so listings are in the HTML.
  • Blog search filters the rendered list in the browser so /blog is not ?search=-dynamic.
  • Empty listings retry, then expire in 60s instead of baking forever.

Earlier in this PR

Tutorial, unpublished/fork filtering, homepage property, locale nav, sitemap/RSS/robots.

Testing

  • browser/lib/src/cms.test.ts — listing filters, cmsEditUrl, sitemap/RSS helpers, cmsPathToSlug / CMS_CDN_CACHE_CONTROL.
  • template.spec.ts Next.js: ISR production build, homepage About, first-byte locale, CDN Cache-Control, in-place edit (assertInPlaceEdit), Data Browser link on the sign-in banner.
  • template.spec.ts SvelteKit: same first-byte locale/content and Cache-Control; footer Edit this page still opens the Data Browser.
Open in Web Open in Cursor 

cursoragent and others added 13 commits August 14, 2026 07:42
Generated Next.js and SvelteKit sites no longer list, search, or route
forks or blog posts with a missing or future published-at. Cmd/Ctrl+E
and an Edit this page link open the Data Browser edit form; --cms-url
covers a split editor origin. Blogpost cover-image and published-at are
recommended so incomplete posts can be saved.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
The empty guide is now the product walkthrough: apply the template,
generate Next.js or SvelteKit, edit from the live site. The Astro
guide no longer tells readers to use a Drive DID as the server URL.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
isListedCmsResource and cmsEditUrl are the tested core of the
generated-site publish filter and edit-from-site URL. Templates keep
local copies so they do not depend on an unreleased lib export.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
`/` now serves the Website resource's homepage property instead of whichever page happens to have path `/`. Nav and the site title keep the current locale prefix so `/nl/blog` → Home is `/nl`. Next.js updates `<html lang>` from the URL, and Svelte blog cards no longer crash when description is missing.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Template e2e now scaffolds with --cms-url pointing at the Data Browser, asserts that origin on Cmd/Ctrl+E, checks Next.js html lang, and clicks Home from /nl/blog. Tutorial and coverage map match.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Next caches `fetch` during `next build`. A collection that is briefly empty after applying the Website template then stays empty on `next start`. The catch-all route is now force-dynamic and the server store uses cache: no-store so blog listings re-query AtomicServer on each request.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
cache: no-store on the store fetch made /_not-found fail static generation (DYNAMIC_SERVER_USAGE). The root layout is now force-dynamic so 404s and listings both re-query AtomicServer.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
isListedCmsResource took `now` as a positional number, and listings used `.filter(isListedCmsResource)`. The index (0, 1, …) became `now`, so every post looked unpublished. `now` is now on an options object.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
`.filter(isListedCmsResource)` is a type error once `now` lives on an options object. Call sites now pass the resource explicitly.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Generated sites need public URL lists that already dropped forks and
unpublished posts. Keep the XML builders next to isListedCmsResource so
the filter contract is unit-tested, not only asserted in Playwright.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Generated Next.js and SvelteKit sites now expose /sitemap.xml, /rss.xml,
and /robots.txt from the same public-content filter as listings. Blog
cards on /nl/blog keep the language prefix instead of dropping back to
the default-language path.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
The Website template e2e now points homepage at About, forks that page
under a leak-detectable title, and asserts sitemap/RSS omit unpublished
posts. Tutorial and coverage map the new public-feed routes.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
@cursor cursor Bot changed the title Headless CMS: edit from the site, hide unpublished posts, tutorial Headless CMS: edit from the site, hide unpublished posts, feeds, tutorial Aug 14, 2026
cursoragent and others added 5 commits August 14, 2026 08:25
SvelteKit e2e failed because a single /query timeout threw out of
page.evaluate instead of retrying. Catch the timeout and open About
from the sidebar when it is already visible.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
A leftover next-server on :3000 made the following e2e run fail with
EADDRINUSE even after kill-port in the previous test's finally block.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Chromium intercepts Control+E, so a synthetic keydown is a weak signal.
The footer Edit this page control uses the same URL; clicking it is the
reliable popup. SvelteKit now registers the hotkey with addEventListener.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
On /nl/blog the test clicked a Home link that had not yet resolved to
/nl, so the URL never left /nl/blog.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Prerender/ISR public paths, send Cache-Control s-maxage instead of
no-store, set html lang from the URL in the initial HTML, and keep
blog search client-side so listings stay cacheable.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
@cursor cursor Bot changed the title Headless CMS: edit from the site, hide unpublished posts, feeds, tutorial Headless CMS: CDN-cacheable pages, edit from the site, hide unpublished posts Aug 14, 2026
cursoragent and others added 5 commits August 14, 2026 08:47
Next.js statically analyzes `export const revalidate` and rejects an
imported constant, which broke `next build`.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
kill-port missed an IPv6 next-server on :3000, so pnpm start failed
with EADDRINUSE after a successful ISR build.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
instrumentation.ts does not run in generateStaticParams workers, so
typed .props (blocks, menu items) were empty in prerendered HTML.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Prerendered files are served by sirv and drop hooks.server.ts headers.
SSR keeps first-byte lang/content and sends s-maxage for shared caches.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
Wire @tomic/edit-mode into generated Next.js sites so Edit this page
and Cmd/Ctrl+E turn headings and body text into contentEditable fields
that save to Atomic on blur. The agent secret stays in localStorage;
Cmd/Ctrl+Shift+E still opens the Data Browser.

Co-authored-by: joepmeindertsma <joepmeindertsma@gmail.com>
@cursor cursor Bot changed the title Headless CMS: CDN-cacheable pages, edit from the site, hide unpublished posts Headless CMS: in-place edit, CDN-cacheable pages, hide unpublished posts Aug 14, 2026
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.

2 participants