<?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: Zaen</title>
    <description>The latest articles on DEV Community by Zaen (@zaen3).</description>
    <link>https://dev.to/zaen3</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%2F4090990%2Fc215dcd2-6943-470f-8227-7b4226a00b30.png</url>
      <title>DEV Community: Zaen</title>
      <link>https://dev.to/zaen3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zaen3"/>
    <language>en</language>
    <item>
      <title>Beaver: a practical CMS layer for Astro SSR</title>
      <dc:creator>Zaen</dc:creator>
      <pubDate>Sun, 23 Aug 2026 15:47:01 +0000</pubDate>
      <link>https://dev.to/zaen3/beaver-a-practical-cms-layer-for-astro-ssr-4k8f</link>
      <guid>https://dev.to/zaen3/beaver-a-practical-cms-layer-for-astro-ssr-4k8f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujlqgj5hrxlyzutz8vh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fujlqgj5hrxlyzutz8vh5.png" alt=" " width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building an Astro website is straightforward until content, editorial workflows, media, and public delivery all need to work together. Beaver brings those pieces into one package: a CMS admin panel and middleware for Astro SSR projects.&lt;/p&gt;

&lt;p&gt;Beaver is designed for teams that want a working content system without turning every public page into a custom integration. Content lives in the CMS, while the website remains a file-based Astro project that developers can shape around the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Beaver provides
&lt;/h2&gt;

&lt;p&gt;The admin panel supports pages and posts with draft and published states, publication dates, slugs, excerpts, descriptions, tags, categories, featured images, galleries, and SEO metadata. Editors can work with rich text, duplicate content, publish or unpublish items in bulk, and remove multiple records when needed.&lt;/p&gt;

&lt;p&gt;The content model is not limited to posts. Beaver uses registries for custom content types, archive layouts, detail layouts, page sections, and menus. That makes it possible to add a case-study or resource library while keeping the same CMS workflow.&lt;/p&gt;

&lt;p&gt;Media is part of the same workflow. The media library stores files with folders, captions, alternative text, metadata, thumbnails, and responsive image variants. Upload validation checks the file signature, MIME type, size, image dimensions, and pixel count. Local storage and Amazon S3 are both supported.&lt;/p&gt;

&lt;p&gt;For sites that need to receive messages, Beaver also includes a public inquiry form. The form can use SMTP delivery, reply-to addresses, Turnstile verification, input validation, and rate limiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start a project in one command
&lt;/h2&gt;

&lt;p&gt;The interactive initializer creates a new Beaver project:&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 @zbeaver/beaver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wizard asks for the project name, starter template, package manager, and Super Admin details. It then creates the Astro project, installs dependencies, generates the configuration, runs the database migration, seeds Beaver and the selected template, and prints the website and admin URLs.&lt;/p&gt;

&lt;p&gt;Beaver supports SQLite, MySQL, and PostgreSQL through Drizzle migrations. SQLite is a convenient default for local development and smaller deployments, while MySQL and PostgreSQL are available when the application needs a separate database service.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small project root, a flexible &lt;code&gt;src/&lt;/code&gt; directory
&lt;/h2&gt;

&lt;p&gt;The initializer keeps the main project configuration familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
├── .env
├── astro.config.mjs
└── src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file contains server-side settings for the database, authentication, storage, email, and cache. Important values include &lt;code&gt;DB_CONNECTION&lt;/code&gt;, &lt;code&gt;DB_DATABASE&lt;/code&gt;, the admin credentials, session and JWT secrets, &lt;code&gt;ADMIN_PATH&lt;/code&gt;, and the storage configuration.&lt;/p&gt;

&lt;p&gt;For local files, configure a path such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STORAGE_TYPE=local
STORAGE_PATH=./public/storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For S3, set the region, bucket, and credentials instead. Both storage backends use the same public &lt;code&gt;/storage/&amp;lt;file&amp;gt;&lt;/code&gt; URL format, so changing the backend does not require changing the website components.&lt;/p&gt;

&lt;p&gt;The public website is built directly in &lt;code&gt;src/&lt;/code&gt;. Layouts provide the shared HTML shell, pages define routes, components render reusable UI, and styles hold the public design tokens and base rules. Astro SSR, React islands, and Tailwind can be used together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registries keep the website adaptable
&lt;/h2&gt;

&lt;p&gt;Beaver does not require developers to hard-code every content route. A content-type registry connects a content type to its archive and detail templates. For example, a &lt;code&gt;case-study&lt;/code&gt; type can use its own grid and reader components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/components/web/content-type-templates/archive/case-study-grid.astro
src/components/web/content-type-templates/detail/case-study-reader.astro
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The matching template IDs live in &lt;code&gt;src/components/web/content-type-templates/registry.json&lt;/code&gt;. The public archive and detail routes read those IDs and resolve the corresponding Astro components automatically.&lt;/p&gt;

&lt;p&gt;Page sections follow the same idea. A section registry describes the available section type, its editable fields, and its item mode. Sections can be single-item or repeatable, and can load published content such as posts. The template includes common sections for heroes, contact forms, FAQs, videos, maps, pricing, showcases, steps, testimonials, banners, and post collections.&lt;/p&gt;

&lt;p&gt;Menus are registry-based too. Navbar, footer, and sidebar groups are defined in the menu registry, while the actual links remain editable CMS data. This keeps navigation content separate from the layout that renders it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render CMS content in Astro
&lt;/h2&gt;

&lt;p&gt;Astro pages, layouts, and components can use the server-side helpers from &lt;code&gt;@zbeaver/beaver/server&lt;/code&gt;. These helpers return published content for the website, so draft and private records are not exposed to unauthenticated visitors.&lt;/p&gt;

&lt;p&gt;To load one published page or post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getPublishedPostByType, sanitizeHtml } from "@zbeaver/beaver/server"

const result = getPublishedPostByType("post", Astro.params.slug || "")
if (!result.success) return new Response("Post not found", { status: 404 })

const post = result.data
---

&amp;lt;article&amp;gt;
  &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
  {post.description &amp;amp;&amp;amp; &amp;lt;div set:html={sanitizeHtml(post.description)} /&amp;gt;}
&amp;lt;/article&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For archives, &lt;code&gt;listPublishedPostsByType&lt;/code&gt; supports pagination and filters for search, categories, tags, custom fields, title, and creation date. Dedicated helpers are also available for search, tag archives, menus, and site settings.&lt;/p&gt;

&lt;p&gt;Rich-text content should be sanitized before it is rendered. Beaver provides &lt;code&gt;sanitizeHtml&lt;/code&gt; for that purpose, and the generated middleware adds security headers, content security policy rules, request-size protection, download handling, and no-cache behavior for admin routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  CLI commands for ongoing work
&lt;/h2&gt;

&lt;p&gt;After the first setup, the Beaver CLI covers common maintenance tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Apply pending migrations&lt;/span&gt;
npx @zbeaver/beaver migrate

&lt;span class="c"&gt;# Validate seed data without writing to the database&lt;/span&gt;
npx @zbeaver/beaver migrate:data ./data/seed.json &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# Seed the system and the Flowstack demo template&lt;/span&gt;
npx @zbeaver/beaver seed flowstack

&lt;span class="c"&gt;# Reset the Super Admin password from .env&lt;/span&gt;
npx @zbeaver/beaver reset superadmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seed data uses a portable JSON shape with &lt;code&gt;settings&lt;/code&gt;, &lt;code&gt;categories&lt;/code&gt;, &lt;code&gt;posts&lt;/code&gt;, &lt;code&gt;pages&lt;/code&gt;, and &lt;code&gt;menus&lt;/code&gt;. Existing records are skipped by default, and &lt;code&gt;--overwrite&lt;/code&gt; can update records that match an existing slug or menu identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful boundary between code and content
&lt;/h2&gt;

&lt;p&gt;Beaver's main strength is the boundary it creates. Editors manage content, metadata, media, and navigation from the admin panel. Developers control the Astro templates, registries, sections, routes, and styles in source control. Server-side helpers connect the two without forcing the website into a fixed visual structure.&lt;/p&gt;

&lt;p&gt;That approach works well for an Astro project that needs a CMS but still wants the speed and control of a code-owned frontend. Start with the generated Flowstack template, replace its public components with the site's own design, and add custom content types only when the content model calls for them.&lt;/p&gt;

&lt;p&gt;For the complete configuration reference and server-side helper details, see the &lt;a href="https://github.com/zaengit/beaver" rel="noopener noreferrer"&gt;Beaver package&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>astro</category>
    </item>
  </channel>
</rss>
