Add a blog, built from pages

A post is a page in the panel's "Blog" category, so it is written,
translated, scheduled and reviewed like any page, and the MCP server
can publish one the same way. Nothing new in the API.

- /blog (/en/blog, /ca/blog): posts newest first, with image, date and
  excerpt.
- A post shows its date and leads back to the blog; its structured data
  is a BlogPosting.
- /blog/rss.xml?lang=xx: one feed per language, announced in <head>.
- Posts stay out of the header and the footer, which links the blog
  once there is any post instead of growing with every one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pigui
2026-09-29 15:45:08 +02:00
parent 86b7485b32
commit fea60a168c
17 changed files with 656 additions and 27 deletions

View File

@@ -164,6 +164,7 @@ landing/src/
routes/api/calls/[slug]/apply.post.ts forwards an application to one call
routes/sitemap.xml.get.ts every published address, from the API's /slugs
routes/robots.txt.get.ts points crawlers at the sitemap
routes/blog/rss.xml.get.ts the blog as RSS, one feed per language
middleware/redirects.ts 301s for slugs renamed in the panel
middleware/not-found-status.ts
styles.scss Material theme, `@pi/ui` sheets, shared primitives
@@ -254,6 +255,20 @@ cannot hold two parameters of different names at the same depth —
Featured pages go in the header after the section anchors; every page is in the
footer.
## The blog
Not a type of its own: **a post is a page in the panel's "Blog" category** —
written, translated, scheduled, reviewed and reachable by the MCP server like
any page. That category has to exist with the slug `blog` in every language
(`core/blog.ts`), because the API filters by the slug in the language asked for.
- `/blog` (`/en/blog`, `/ca/blog`) lists them newest first.
- A post shows its date and leads back to the blog; its structured data is a
`BlogPosting`.
- `/blog/rss.xml?lang=xx` is the feed, one per language, announced in `<head>`.
- The footer does not list posts one by one — it would grow without end — but
links the blog once there is any.
## Search engines and link previews
- **`/sitemap.xml`** comes from the API's `/slugs` — one request for every
@@ -270,8 +285,8 @@ footer.
own picture (the logo otherwise), and schema.org JSON-LD — `Organization` on
the landing, `WebPage`, `CreativeWork`, `Person` or `Service` elsewhere.
In development only `/api/*` reaches Nitro, so `/sitemap.xml` and
`/robots.txt` answer 404 under `npm start`; build and run `npm run preview` to
In development only `/api/*` reaches Nitro, so `/sitemap.xml`, `/robots.txt`
and `/blog/rss.xml` answer 404 under `npm start`; build and run `npm run preview` to
see them. Middleware does run in development.
## Rendering and caching