<?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: Gerwin Weiher</title>
    <description>The latest articles on DEV Community by Gerwin Weiher (@gerwin_weiher_7bf091b81c9).</description>
    <link>https://dev.to/gerwin_weiher_7bf091b81c9</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3878687%2Fba964711-d259-45a0-ada2-968b16010df2.jpg</url>
      <title>DEV Community: Gerwin Weiher</title>
      <link>https://dev.to/gerwin_weiher_7bf091b81c9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gerwin_weiher_7bf091b81c9"/>
    <language>en</language>
    <item>
      <title>I built a CMS that fits in one file — here's why and how</title>
      <dc:creator>Gerwin Weiher</dc:creator>
      <pubDate>Tue, 14 Apr 2026 13:15:59 +0000</pubDate>
      <link>https://dev.to/gerwin_weiher_7bf091b81c9/i-built-a-cms-that-fits-in-one-file-heres-why-and-how-h1f</link>
      <guid>https://dev.to/gerwin_weiher_7bf091b81c9/i-built-a-cms-that-fits-in-one-file-heres-why-and-how-h1f</guid>
      <description>&lt;p&gt;Every CMS I've tried for Astro comes with the same overhead: a database to provision, a media CDN to configure, an auth service to wire up. For small sites and personal projects, that's a lot of ops for not a lot of content.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Orbiter&lt;/strong&gt; — a CMS where everything lives in one &lt;code&gt;.pod&lt;/code&gt; file. Content, media, schema, users, sessions. All of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The idea
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-site/
├── astro.config.mjs
├── content.pod    ← your entire CMS
└── src/pages/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.pod&lt;/code&gt; extension is just SQLite. You can open it with any SQLite browser. Copy it, your whole site moves with it. Back it up with &lt;code&gt;cp&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @a83/orbiter-integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// astro.config.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;orbiter&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;@a83/orbiter-integration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;orbiter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./content.pod&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Orbiter injects a full admin UI at &lt;code&gt;/orbiter&lt;/code&gt; using Astro's &lt;code&gt;injectRoute&lt;/code&gt; — no files added to your &lt;code&gt;src/pages&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading content
&lt;/h3&gt;

&lt;p&gt;The virtual module mirrors Astro's own content collection API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import { getCollection, getEntry } from 'orbiter:collections';

const posts = await getCollection('posts');
const post  = await getEntry('posts', 'my-first-post');
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For multilingual sites there are &lt;code&gt;getLocaleCollection&lt;/code&gt; and &lt;code&gt;getLocaleEntry&lt;/code&gt; helpers using a &lt;code&gt;slug--locale&lt;/code&gt; convention.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's in the admin
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Collection editor&lt;/strong&gt; — rich text (Markdown + live preview), all common field types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema editor&lt;/strong&gt; — add or change fields without migrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media library&lt;/strong&gt; — stored as BLOBs directly in the pod&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version history&lt;/strong&gt; — every save creates a snapshot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON API&lt;/strong&gt; — &lt;code&gt;GET /orbiter/api/[collection]&lt;/code&gt; with optional Bearer token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import&lt;/strong&gt; — WordPress WXR and Markdown files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub sync&lt;/strong&gt; — for serverless environments where the filesystem is ephemeral&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PWA&lt;/strong&gt; — the admin is installable on mobile&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The tradeoffs I'm honest about
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;SQLite BLOB storage&lt;/strong&gt; is convenient but not for large media libraries. If you have thousands of high-res images, this isn't the right tool. For a typical content site it's fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serverless filesystems&lt;/strong&gt; are ephemeral. Netlify and Vercel don't persist writes between deploys. The GitHub sync mode works around this — pack your pod and media into a git commit, rebuild on push — but it adds steps. I'm still not 100% happy with this DX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WAL mode&lt;/strong&gt; helps with concurrent reads but SQLite is still SQLite. For a site with hundreds of simultaneous admin users this is the wrong choice. For a team of 1–5 editing content, it's perfectly fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not Keystatic / Decap / Tina?
&lt;/h3&gt;

&lt;p&gt;All great tools. The difference: they store content as files in your git repo, which means your database &lt;em&gt;is&lt;/em&gt; your git history. That's a valid approach but it means every content change is a commit, and media files bloat your repo fast.&lt;/p&gt;

&lt;p&gt;Orbiter's approach is the opposite: one binary blob, no git history for content, trivial to move.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/aeon022/orbiter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;orbiter &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run seed &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin at &lt;code&gt;http://localhost:8080/orbiter&lt;/code&gt; — login: &lt;code&gt;admin / admin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or scaffold a new 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 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @a83/orbiter-cli
orbiter init my-site
&lt;span class="nb"&gt;cd &lt;/span&gt;my-site &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub: &lt;a href="https://github.com/aeon022/orbiter" rel="noopener noreferrer"&gt;https://github.com/aeon022/orbiter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>cms</category>
      <category>sqlite</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
