Open-source CMS for Astro and Next.js

Polaris CMS

My own CMS, built into the site: write in two languages, see each post's SEO score as you type, schedule it, and let AI agents publish under the same rules.

Status Open source under the MIT license. The public release on GitHub and npm is in preparation.

In short

Polaris CMS is the content system I run my own sites on. It is not a hosted service: it lives inside the site's own Astro or Next.js app, keeps posts and images in the site's PostgreSQL database and adds an admin at /studio.

It started as the blog editor inside PolishPal, where it got its name. For the next sites I ported it to Astro and Next.js instead of picking a hosted CMS, and today it runs six sites: PolishPal, PlantAlong, Olfaria, Grzybolog, Yolkday and this one.

I built it because I wanted the SEO rules, the publishing schedule and the API for my AI agents right next to the content, under the same strict security policy as the rest of the site, with the data in plain Postgres that can move anywhere.

What's inside

Everything below runs today on this site and on the five products in my portfolio.

  1. An editor with a live SEO score

    Markdown with write, split and preview views; the preview uses the site's own renderer and stylesheet. Tables, footnotes, highlighted code, figures with captions, and chart blocks drawn as SVG with their numbers in a data table. Eleven checks run as you type, and the score is the share that pass:

    • Focus keyword in the SEO title
    • Focus keyword in the meta description
    • Focus keyword in the URL slug
    • Focus keyword in the first paragraph
    • Focus keyword in a subheading (H2 or H3)
    • Keyword density from 0.5% to 2.5%
    • At least 600 words
    • SEO title of 60 characters or fewer
    • Meta description of 50 to 160 characters
    • At least one internal link
    • At least one image with alt text
  2. Scheduling without a cron job

    A post is a draft, scheduled or published. A scheduled post goes live at its time on its own: public pages treat a due post as live, and a housekeeping tick then flips its status and pings IndexNow.

  3. Two languages, paired

    Every post has a language. Its translation is picked in the editor, or sent through the API as translationOf, and hreflang links the two only once both are live.

  4. Media in the database

    Uploads are converted to WebP, at most 1600 px wide (an SVG becomes a lossless WebP), and stored in PostgreSQL. They are served from /media/ with immutable caching, so there is no separate file storage to run.

  5. SEO plumbing

    RSS per language, a live sitemap, generated share images, JSON-LD, IndexNow pings on publish, and Google Search Console URL inspection from the admin when a service account is set.

  6. Backups and restore

    Every save and every delete keeps a snapshot of the previous version, a full backup is taken daily, and any snapshot can be downloaded as JSON or restored in place.

  7. An API for AI agents

    Keys are created in the admin, stored only as a hash, and a key can't create or revoke keys. Agents list, read, save, schedule and delete posts and upload images, and what they write gets the same SEO score as a person's post. Actions: list, list-all, get, save, set-status, delete, recompute-scores, plus POST /api/cms/upload for images.

    POST /api/cms/posts
    Authorization: Bearer $KEY
    
    { "action": "save",
      "post": { "lang": "en", "title": "…", "content": "## …",
                "status": "scheduled",
                "publishedAt": "2026-12-01T08:00:00Z" } }
  8. Secure under a strict CSP

    Sign-in with rate limiting and a signed session cookie. The admin runs under a Content Security Policy with no inline scripts or styles, this site's included, and drafts are never public.

  9. A plain stack

    Astro or Next.js, React 19 for the admin, Drizzle ORM and PostgreSQL 16 or newer. Migrations run when the app starts, and it runs wherever Node runs, in Docker or not. The starter kits add optional modules: comments with moderation and ad slots.

Inside the admin

Screens from this site's own admin, with the real posts from this blog.

Polaris CMS articles list: four published posts in English and Polish, each with an SEO score of 100, its language, URL, translation link, status, category and date.
Articles: both languages in one list, with the SEO score, status and translation of each post.
Polaris CMS editor in split view: Markdown on the left, a live preview in the site's own typography on the right, and the language, translation and publish settings beside it.
The editor in split view, with language, translation and schedule beside the text.
The editor's SEO panel: focus keyword, meta description, a score of 100 with 11 of 11 checks passing, a search result preview and the list of checks.
The SEO panel: eleven checks, scored as you type.
Polaris CMS media library: an upload area and a grid of WebP images, each with its size, the post that uses it and buttons to copy its URL or Markdown.
The media library: every image converted to WebP and stored with the site.

Coming with v1

Install

The public release will install like any npm package. These commands are planned for v1 and don't work yet; until then I set up Polaris CMS myself as part of a website project.

  • A new site

    An Astro or Next.js starter with Polaris CMS built in.

    npm create polaris-cms@latest my-site
  • An existing Astro site

    One integration adds /studio, the API routes and the database migrations.

    npm install polaris-cms
    
    # astro.config
    integrations: [polaris()]
  • An existing Next.js site

    Install the package and add one route file that re-exports its handlers.

    npm install polaris-cms

Coming with v1 Updates with npm update. It needs PostgreSQL, runs anywhere Node runs, and a Docker recipe will be included.

Want Polaris CMS on your site?

The docs and release notes will live on the project site. If you need it before the public release, write to me: I set it up as part of a website or a blog.