<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kanawut korsang</title>
    <description>The latest articles on DEV Community by kanawut korsang (@kanawoot_korsang_5be7c94a).</description>
    <link>https://dev.to/kanawoot_korsang_5be7c94a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4187795%2F2e1058e2-fd9f-4653-b986-fd098602c098.jpeg</url>
      <title>DEV Community: kanawut korsang</title>
      <link>https://dev.to/kanawoot_korsang_5be7c94a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kanawoot_korsang_5be7c94a"/>
    <language>en</language>
    <item>
      <title>Building a Payload-style CMS that lives inside your Nuxt app</title>
      <dc:creator>kanawut korsang</dc:creator>
      <pubDate>Wed, 21 Oct 2026 05:48:02 +0000</pubDate>
      <link>https://dev.to/kanawoot_korsang_5be7c94a/building-a-payload-style-cms-that-lives-inside-your-nuxt-app-30ce</link>
      <guid>https://dev.to/kanawoot_korsang_5be7c94a/building-a-payload-style-cms-that-lives-inside-your-nuxt-app-30ce</guid>
      <description>&lt;p&gt;Nuxt has a great Git-based CMS. What it doesn't have is a mature CMS that lives inside your app and keeps content in a database, the way Payload does for Next.js. Payload runs &lt;em&gt;inside&lt;/em&gt; a Next.js app. With Nuxt, it still runs &lt;em&gt;next to&lt;/em&gt; your app.&lt;br&gt;
I build websites for clients, some in Nuxt and some in Next.js, and I kept wanting that missing piece. So I built it: &lt;strong&gt;Easy CMS&lt;/strong&gt;, an open-source (MIT) headless CMS that you install into a Nuxt or Next.js app with npm. Next.js developers are welcome too: both frameworks get the same features.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;My clients build their sites in Vue or React, and they want a back office their editors can use, in Thai and in English. For a developer, that back office should be just another npm package: versioned with the app, reviewed in pull requests and deployed with it.&lt;br&gt;
Here's what a Nuxt developer could pick from (as of October 2026):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nuxt Content + Nuxt Studio.&lt;/strong&gt; It's free, MIT and self-hosted, and it's excellent when your content is Markdown. But it's Git-based: an edit becomes a commit. It doesn't give you records in a database, roles, or data that users create.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload.&lt;/strong&gt; It's great, and you can &lt;a href="https://payloadcms.com/docs/local-api/outside-nextjs" rel="noopener noreferrer"&gt;call its Local API from Nuxt&lt;/a&gt;. But its admin is React and needs a Next.js (or, in the v4 beta, TanStack Start) host. That means two apps, two deploys, and an admin you can't extend with Vue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directus or Strapi.&lt;/strong&gt; Both are solid, but each is another server to deploy, update and keep in sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanity or Contentful.&lt;/strong&gt; Both work well, but your content lives on their cloud.
What I wanted was what Payload gives Next.js developers: content defined in code, an admin included, and one app to deploy. I wanted it for Nuxt too.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;Easy CMS runs inside your app's server. You describe your content in &lt;code&gt;easy-cms.config.ts&lt;/code&gt;, and you get an admin at &lt;code&gt;/admin&lt;/code&gt;, a typed Local API for your pages and a REST API at &lt;code&gt;/api/cms&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgup0145imryke6mdn21j.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgup0145imryke6mdn21j.webp" alt="The Easy CMS dashboard" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it in two minutes
&lt;/h2&gt;

&lt;p&gt;Inside an existing Nuxt 4 or Next.js 15+ app (Node.js 22.12 or newer, 22.19 with Nuxt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create easy-cms@latest
npm run dev   &lt;span class="c"&gt;# open http://localhost:3000/admin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command detects Nuxt or Next.js and installs the packages. It writes &lt;code&gt;easy-cms.config.ts&lt;/code&gt; with a sample &lt;code&gt;posts&lt;/code&gt; collection, puts a random secret in &lt;code&gt;.env&lt;/code&gt;, and wires up the framework: a module for Nuxt, route handlers for Next.js. When you open &lt;code&gt;/admin&lt;/code&gt;, it asks you to create the first admin.&lt;br&gt;
The config is plain TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// easy-cms.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@easy-cms/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sqlite&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@easy-cms/db-sqlite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EASY_CMS_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sqlite&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file:./cms.db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;drafts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;richText&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading content in Nuxt is a server route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/api/posts.get.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;useEasyCMS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;find&lt;/code&gt; returns only published documents by default. &lt;code&gt;post.title&lt;/code&gt; is typed as &lt;code&gt;string&lt;/code&gt; because the config says it's a required text field. In development the database schema follows the config: save the file, and the admin shows the new fields.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5onl4g11pvj7koz0html.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5onl4g11pvj7koz0html.webp" alt="Editing a post in the admin" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
In Next.js, you call &lt;code&gt;getEasyCMS(config)&lt;/code&gt; in a Server Component. With no framework at all, &lt;code&gt;npm create easy-cms@latest my-cms&lt;/code&gt; gives you a standalone server on port 4000 that any frontend can call.&lt;br&gt;
The admin covers what editors usually ask for: drafts and scheduled publishing, version history with restore, live preview, a media library with folders, roles set in the admin, single sign-on, an audit log, backups and a ⌘K command palette.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F782a20h3teny1ym5923p.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F782a20h3teny1ym5923p.webp" alt="The command palette" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────── your Nuxt / Next.js app ──────────────────┐
│  pages ──► Local API (typed)      browsers ──► /api/cms      │
│  editors ──► /admin                                          │
│                  @easy-cms/nuxt | @easy-cms/next             │
│                        @easy-cms/core                        │
│          SQLite / Postgres (ecms_ tables) · uploads          │
└──────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The core is framework-free.&lt;/strong&gt; &lt;code&gt;@easy-cms/core&lt;/code&gt; is plain TypeScript built on the Web-standard &lt;code&gt;Request&lt;/code&gt; and &lt;code&gt;Response&lt;/code&gt;. Config, validation, access control, hooks, drafts and both APIs all live there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each adapter is thin.&lt;/strong&gt; The Nuxt module and the Next.js route handlers mostly mount one handler. That's why both frameworks get the same features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Local API skips HTTP.&lt;/strong&gt; Your pages call &lt;code&gt;cms.find()&lt;/code&gt; directly, and you deploy one app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your data stays in your database.&lt;/strong&gt; Easy CMS uses SQLite (a file, or Turso) or Postgres, through Drizzle. It writes to real tables and columns with an &lt;code&gt;ecms_&lt;/code&gt; prefix, so you can still query them with SQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The admin comes prebuilt.&lt;/strong&gt; It's a Vue app that reads your config from the API and draws the forms, so you never build it.
This has a cost: every feature has to work through two frameworks and a standalone server. So adapters stay as thin as possible, and end-to-end tests cover all three.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it fits
&lt;/h2&gt;

&lt;p&gt;Here's how it compares, as of October 2026. Each of these tools is good at something:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Runs inside a Nuxt app&lt;/th&gt;
&lt;th&gt;Content stored in&lt;/th&gt;
&lt;th&gt;Admin UI&lt;/th&gt;
&lt;th&gt;Access control&lt;/th&gt;
&lt;th&gt;License&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nuxt Content + Studio&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Git (files)&lt;/td&gt;
&lt;td&gt;Vue&lt;/td&gt;
&lt;td&gt;Login, no roles&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload&lt;/td&gt;
&lt;td&gt;Local API, or a separate app&lt;/td&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;td&gt;In code&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Directus&lt;/td&gt;
&lt;td&gt;Separate server&lt;/td&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Vue&lt;/td&gt;
&lt;td&gt;Roles in the admin&lt;/td&gt;
&lt;td&gt;Source-available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strapi&lt;/td&gt;
&lt;td&gt;Separate server&lt;/td&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;React&lt;/td&gt;
&lt;td&gt;Roles in the admin&lt;/td&gt;
&lt;td&gt;MIT (Community Edition)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Easy CMS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ (Next.js too)&lt;/td&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Vue&lt;/td&gt;
&lt;td&gt;Roles in the admin + rules in code&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your content is Markdown that lives in Git, use Nuxt Studio. If you're on Next.js and want the biggest ecosystem, Payload is the safe choice. Easy CMS is for content that lives in a database, with roles and an admin, inside a Nuxt or Next.js app. Other young projects are exploring this space too, like &lt;a href="https://github.com/xleddyl/nuxt-cms" rel="noopener noreferrer"&gt;nuxt-cms&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it was built: with AI, and with receipts
&lt;/h2&gt;

&lt;p&gt;Easy CMS 1.0 came out on October 17, 22 days after the first commit. That's fast, and you should be suspicious of it. Here's the honest picture.&lt;br&gt;
I build it alone, in my spare time. About half of the more than 400 commits were co-written with Claude Code, and the trailers are in the git history for anyone to check. What keeps that safe is the process around the code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;55 architecture decision records.&lt;/strong&gt; Every decision that matters has its reasons written down, in Thai, my first language.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;800+ tests.&lt;/strong&gt; There are unit and integration tests, Playwright end-to-end tests, smoke tests of &lt;code&gt;create-easy-cms&lt;/code&gt; with npm, pnpm, Yarn and Bun, and upgrade tests. The upgrade tests migrate databases created by older releases on every CI run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A self-review against OWASP ASVS Level 1.&lt;/strong&gt; It's my own review, not an external audit.
That review found the worst bug so far. In the multi-tenant plugin, one helper decided two things: whether a user could &lt;em&gt;read&lt;/em&gt; every tenant, and whether they had &lt;em&gt;access&lt;/em&gt; to every tenant. The code looked reasonable. But once sites could have members, a customer who signed up on a shop passed that check. They could then edit other users, including the admin's password. The review flagged it as critical. The fix split the helper in two, added a test that fails on the old code, and shipped in 0.47.3 the same day.
Less dramatically, a rename of config options before 1.0 touched 139 files and missed three fields in the starter template. CI caught it when the template failed to build.
AI makes it cheap to write code that looks right. Tests, written decisions and reviews are how you find the code that only looks right.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When it's not a fit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Editors can't change the content model.&lt;/strong&gt; It's code, so a developer has to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No edge runtimes.&lt;/strong&gt; It needs Node.js 22.12 or newer (22.19 with Nuxt). MySQL isn't supported either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No big marketplace.&lt;/strong&gt; There aren't many ready-made integrations yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some plugins are experimental.&lt;/strong&gt; The shop, GraphQL and MCP plugins may still change in a minor release. Everything else follows the &lt;a href="https://easy-cms-website.vercel.app/docs/versioning" rel="noopener noreferrer"&gt;1.0 stability promise&lt;/a&gt;: breaking changes only in a major release, with an upgrade guide.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it, and tell me where it got in your way
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;npm create easy-cms@latest&lt;/code&gt; in a Nuxt or Next.js app, or click a Deploy button in the README to get a blog with its admin on Vercel or Netlify. Then tell me what got in your way in the &lt;a href="https://github.com/maritonx/easy-cms/issues/137" rel="noopener noreferrer"&gt;1.0 feedback issue&lt;/a&gt;. A rough sentence is fine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/maritonx/easy-cms" rel="noopener noreferrer"&gt;github.com/maritonx/easy-cms&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs (English and Thai): &lt;a href="https://easy-cms-website.vercel.app/docs" rel="noopener noreferrer"&gt;easy-cms-website.vercel.app/docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vue</category>
      <category>nextjs</category>
      <category>nuxt</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
