<?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: Varinder Singh</title>
    <description>The latest articles on DEV Community by Varinder Singh (@varinder_singh_c541dcb059).</description>
    <link>https://dev.to/varinder_singh_c541dcb059</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%2F3857382%2F45ac6bee-e7a4-434e-9aef-1d6744cb6893.jpg</url>
      <title>DEV Community: Varinder Singh</title>
      <link>https://dev.to/varinder_singh_c541dcb059</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/varinder_singh_c541dcb059"/>
    <language>en</language>
    <item>
      <title>How to manage multiple websites from one Astro monorepo</title>
      <dc:creator>Varinder Singh</dc:creator>
      <pubDate>Sat, 18 Apr 2026 07:31:14 +0000</pubDate>
      <link>https://dev.to/varinder_singh_c541dcb059/how-to-manage-multiple-websites-from-one-astro-monorepo-23h4</link>
      <guid>https://dev.to/varinder_singh_c541dcb059/how-to-manage-multiple-websites-from-one-astro-monorepo-23h4</guid>
      <description>&lt;p&gt;If you're an agency, freelancer, or company running more than a couple of websites, you've hit the maintenance multiplier problem: every improvement has to be replicated across every site. A header fix becomes six PRs. A design refresh becomes a month-long project.&lt;/p&gt;

&lt;p&gt;This article walks through how we solved it with a shared-component monorepo — and how you can use the same approach. We've open-sourced the entire setup as &lt;strong&gt;&lt;a href="https://github.com/Indivar/astro-fleet" rel="noopener noreferrer"&gt;Astro Fleet&lt;/a&gt;&lt;/strong&gt;. Version 2.2 just shipped this week with a new CLI, a working CMS example, and SEO defaults baked into the shared layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;We maintain websites for multiple brands. Each site started as its own repo with its own copy of common components: header, footer, contact form, CTA block, SEO tags.&lt;/p&gt;

&lt;p&gt;Within six months:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The header in Site A had a mobile fix that Sites B–F didn't&lt;/li&gt;
&lt;li&gt;Site C had an improved footer layout that nobody propagated&lt;/li&gt;
&lt;li&gt;Three sites had slightly different versions of the same contact form&lt;/li&gt;
&lt;li&gt;Updating the company colour palette meant editing CSS in six places&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: shared components in a monorepo
&lt;/h2&gt;

&lt;p&gt;The idea is simple: components that appear on every site live in one place. Each site imports them and brands them with its own design tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;astro-fleet/
├── packages/
│   ├── config/                  ← design token presets (colours, fonts, spacing)
│   ├── shared-ui/               ← 22 components + 3 layouts
│   └── create-astro-fleet/      ← the scaffolding CLI
├── sites/
│   ├── client-a.com/            ← pages, site config, deploys independently
│   ├── client-b.com/            ← different brand, same components
│   └── client-c.com/
└── turbo.json                   ← Turborepo orchestrates builds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key principle:&lt;/strong&gt; components are brand-agnostic. They read CSS custom properties (&lt;code&gt;--color-primary&lt;/code&gt;, &lt;code&gt;--font-heading&lt;/code&gt;, etc.) instead of hardcoding colours. The design preset sets those variables. Same component, different look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Scaffold a new fleet with the CLI
&lt;/h2&gt;

&lt;p&gt;The fastest way to start is with the &lt;code&gt;create-astro-fleet&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx create-astro-fleet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Also works with &lt;code&gt;npm create astro-fleet&lt;/code&gt;, &lt;code&gt;pnpm create astro-fleet&lt;/code&gt;, or &lt;code&gt;yarn create astro-fleet&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;The CLI prompts you for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A directory name&lt;/li&gt;
&lt;li&gt;Your first site's domain (e.g. &lt;code&gt;acme.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A design preset (&lt;code&gt;corporate&lt;/code&gt;, &lt;code&gt;saas&lt;/code&gt;, or &lt;code&gt;warm&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Whether to keep the three demo sites as reference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skip the prompts with flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx create-astro-fleet my-agency &lt;span class="nt"&gt;--domain&lt;/span&gt; acme.com &lt;span class="nt"&gt;--preset&lt;/span&gt; saas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&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="nb"&gt;cd &lt;/span&gt;my-agency
bun &lt;span class="nb"&gt;install
&lt;/span&gt;bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're running your first site in about 60 seconds. Prefer to clone the repo directly? That's still supported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Indivar/astro-fleet.git
&lt;span class="nb"&gt;cd &lt;/span&gt;astro-fleet
bun &lt;span class="nb"&gt;install
&lt;/span&gt;bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Add more sites to the fleet
&lt;/h2&gt;

&lt;p&gt;Each new site gets its own directory under &lt;code&gt;sites/&lt;/code&gt; and deploys independently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx create-astro-fleet add newclient.com warm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scaffolds &lt;code&gt;sites/newclient.com/&lt;/code&gt; with the Warm preset applied (cream/amber palette, serif display typography). The scaffolder:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copies the starter template&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;astro.config.mjs&lt;/code&gt; with your domain&lt;/li&gt;
&lt;li&gt;Applies the design preset&lt;/li&gt;
&lt;li&gt;Sets the site name in &lt;code&gt;site-config.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;(The old &lt;code&gt;./scripts/new-site.sh&lt;/code&gt; still works for anyone who prefers bash.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Customise the site identity
&lt;/h2&gt;

&lt;p&gt;Every site has one file that controls its identity: &lt;code&gt;src/lib/site-config.ts&lt;/code&gt;.&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SITE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Acme Analytics&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TAGLINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product analytics for modern teams&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;navigation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MenuItem&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/services/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;children&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Event Pipeline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/services/&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Session Replay&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/services/&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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pricing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/services/&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Docs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/about/&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign in&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/contact/&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;footerColumns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FooterColumn&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// ... your footer structure&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contactInfo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ContactInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello@acme.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+1 (555) 000-0000&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socialLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SocialLink&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&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;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;linkedin&lt;/span&gt;&lt;span class="dl"&gt;'&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;https://linkedin.com/company/acme&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;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;'&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;https://twitter.com/acme&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;Edit this one file and the header, footer, mobile menu, and breadcrumbs all update across the entire site. The social links also flow into the auto-generated JSON-LD graph (more on that in Step 6).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Build pages with shared components
&lt;/h2&gt;

&lt;p&gt;Pages import components from the shared library and pass content via props:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import BaseLayout from '@astro-fleet/shared-ui/src/layouts/BaseLayout.astro';
import FAQ from '@astro-fleet/shared-ui/src/components/FAQ.astro';
import PricingTable from '@astro-fleet/shared-ui/src/components/PricingTable.astro';
import CTABlock from '@astro-fleet/shared-ui/src/components/CTABlock.astro';
import { SAAS } from '@astro-fleet/config/tokens';
import { SITE_NAME, navigation, footerColumns, socialLinks } from '../lib/site-config';
---

&amp;lt;BaseLayout title="Pricing" siteName={SITE_NAME} navigation={navigation}
  footerColumns={footerColumns} socialLinks={socialLinks} designTokens={SAAS}&amp;gt;

  &amp;lt;PricingTable
    heading="Simple pricing"
    tiers={[
      { name: 'Free', price: '$0', period: 'mo',
        features: [{ text: '10K events', included: true }],
        ctaHref: '/signup' },
      { name: 'Pro', price: '$49', period: 'mo', highlighted: true,
        badge: 'Popular',
        features: [{ text: 'Unlimited events', included: true }],
        ctaHref: '/signup' },
    ]}
  /&amp;gt;

  &amp;lt;FAQ items={[
    { question: 'Can I switch plans?', answer: 'Yes, anytime.' },
  ]} /&amp;gt;

  &amp;lt;CTABlock heading="Start free today"
    primaryButton={{ text: 'Sign up', href: '/signup' }} /&amp;gt;

&amp;lt;/BaseLayout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every component adapts to your preset automatically. No CSS to write for branding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: The design token system
&lt;/h2&gt;

&lt;p&gt;Tokens are TypeScript objects that define a site's visual identity:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SAAS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DesignTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;colorPrimary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#0a0f14&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;colorSecondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1a1f2e&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;colorAccent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#34d399&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;colorBackground&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#0d1117&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;colorText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#e6edf3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;colorCta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#10b981&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fontHeading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sora&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fontBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Inter, system-ui, sans-serif&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;These get converted to CSS custom properties by &lt;code&gt;BaseLayout&lt;/code&gt; and injected into &lt;code&gt;:root&lt;/code&gt;. Every component reads &lt;code&gt;var(--color-primary)&lt;/code&gt;, &lt;code&gt;var(--font-heading)&lt;/code&gt;, etc. — so switching from Corporate to SaaS is a one-line change in your page's import.&lt;/p&gt;

&lt;p&gt;Three presets ship out of the box. Creating your own is just defining a new object that conforms to the &lt;code&gt;DesignTokens&lt;/code&gt; interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: SEO by default
&lt;/h2&gt;

&lt;p&gt;This is new in v2.2 and it's where the starter genuinely saves you weeks of work.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BaseLayout&lt;/code&gt; automatically emits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A linked JSON-LD &lt;code&gt;@graph&lt;/code&gt;&lt;/strong&gt; with &lt;code&gt;WebSite&lt;/code&gt; + &lt;code&gt;Organization&lt;/code&gt; nodes, wired with &lt;code&gt;@id&lt;/code&gt; references so crawlers can walk the relationships. The &lt;code&gt;Organization.sameAs&lt;/code&gt; is derived from your site-config's &lt;code&gt;socialLinks&lt;/code&gt; array. Your logo becomes an &lt;code&gt;ImageObject&lt;/code&gt; referenced by &lt;code&gt;@id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full robots directives&lt;/strong&gt; on indexable pages (&lt;code&gt;max-snippet:-1, max-image-preview:large, max-video-preview:-1&lt;/code&gt;) so Google can show rich snippets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canonical URL&lt;/strong&gt; derived from &lt;code&gt;Astro.site&lt;/code&gt; — UTM-stripped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Graph&lt;/strong&gt; with proper &lt;code&gt;og:image:width/height/alt&lt;/code&gt; when you pass an image, and Twitter card defaults that match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;&amp;lt;link rel="icon"&amp;gt;&lt;/code&gt;&lt;/strong&gt; pointing at &lt;code&gt;/favicon.svg&lt;/code&gt; (every site ships one).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And in the site's &lt;code&gt;public/&lt;/code&gt; directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;_headers&lt;/code&gt;&lt;/strong&gt; with a 1-year immutable cache on &lt;code&gt;/_astro/*&lt;/code&gt; and &lt;code&gt;No-Vary-Search&lt;/code&gt; to strip UTM params from the CDN cache key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;robots.txt&lt;/code&gt;&lt;/strong&gt; referencing the sitemap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Page-level JSON-LD (Article, Product, BreadcrumbList) merges cleanly into the auto-generated &lt;code&gt;@graph&lt;/code&gt; — just pass it as &lt;code&gt;structuredData&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;BaseLayout
  title={entry.data.title}
  description={entry.data.summary}
  siteName={SITE_NAME}
  ogType="article"
  structuredData={{
    '@type': 'Article',
    headline: entry.data.title,
    datePublished: entry.data.publishedAt.toISOString(),
    author: { '@type': 'Person', name: entry.data.author },
  }}
  /* ...layout props */
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For optional SEO add-ons (per-page OG images with Satori, git-based sitemap lastmod, &lt;code&gt;llms.txt&lt;/code&gt;, IndexNow, FuzzyRedirect, view transitions), see &lt;code&gt;docs/seo-recipes.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Adding a CMS (so clients can edit their own content)
&lt;/h2&gt;

&lt;p&gt;This is the other big new piece in v2.2. The &lt;strong&gt;Meridian demo&lt;/strong&gt; ships with a working Keystatic integration — a git-based CMS where content stays as markdown in your repo, editors log in via a web UI at &lt;code&gt;/keystatic&lt;/code&gt;, and changes commit back to git.&lt;/p&gt;

&lt;p&gt;The architecture is two files plus content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sites/meridian-advisory.com/
├── keystatic.config.ts          ← field schema, validation, storage
├── src/content.config.ts        ← Astro content collection mirror
└── src/content/insights/*/index.mdoc  ← actual entries, committed to git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;keystatic.config.ts&lt;/code&gt; defines what editors can type:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;collection&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;@keystatic/core&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;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&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="na"&gt;insights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Insights&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;slugField&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/content/insights/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;contentField&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slug&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&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;validation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&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="na"&gt;publishedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Published at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Summary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;multiline&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;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markdoc&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;label&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&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;Run &lt;code&gt;bun run dev --filter=meridian-advisory.com&lt;/code&gt;, open &lt;code&gt;/keystatic&lt;/code&gt;, and you get a full admin UI. Save an entry and it writes a real markdown file to disk that git tracks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The caveat to understand before rolling this out to clients:&lt;/strong&gt; Keystatic's auth is binary — "can you push to this GitHub repo, yes or no?" It doesn't do row-level permissions. In a monorepo, that means everyone with access to the admin can edit every site. Fine for an agency team editing client sites in-house. Not fine for giving each client their own isolated login.&lt;/p&gt;

&lt;p&gt;The full walkthrough (including production editing with GitHub mode, and alternatives — Sanity, Directus, Decap, Payload, Tina) is in &lt;a href="https://github.com/Indivar/astro-fleet/blob/main/docs/adding-a-cms.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/adding-a-cms.md&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What ships in the component library
&lt;/h2&gt;

&lt;p&gt;22 components, each with typed Props, scoped styles, and zero third-party dependencies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Page structure:&lt;/strong&gt; Header, Footer, BaseLayout, IndustryLayout, ProductLayout, Breadcrumb, SEOHead&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content sections:&lt;/strong&gt; HeroSlider, StatsBar, FeatureGrid, ServiceCard, ProductCard, Timeline, TeamGrid, TestimonialSlider, LogoCloud, TrustBar&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conversion:&lt;/strong&gt; CTABlock, PricingTable, ComparisonTable, ContactForm, Newsletter, FAQ&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Utility:&lt;/strong&gt; Banner, SectionDivider&lt;/p&gt;

&lt;p&gt;Only 2 of 22 use any JavaScript (the carousel sliders). Everything else is pure HTML + CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying independently
&lt;/h2&gt;

&lt;p&gt;Each site builds independently via Turborepo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run build &lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acme.com
wrangler pages deploy sites/acme.com/dist &lt;span class="nt"&gt;--project-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acme
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turborepo caches builds, so if only one site changed, only that site rebuilds. A full fleet of 10 sites builds in seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it in action
&lt;/h2&gt;

&lt;p&gt;We built three demo sites to show the range:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Preset&lt;/th&gt;
&lt;th&gt;Demo&lt;/th&gt;
&lt;th&gt;What it looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Corporate&lt;/td&gt;
&lt;td&gt;&lt;a href="https://astro-fleet-meridian.pages.dev" rel="noopener noreferrer"&gt;astro-fleet-meridian.pages.dev&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Consulting firm — editorial hero, stats strip, numbered practice areas, &lt;strong&gt;live CMS-backed /insights blog&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;&lt;a href="https://astro-fleet-flux.pages.dev" rel="noopener noreferrer"&gt;astro-fleet-flux.pages.dev&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Dev tool — dark theme, code mockup, pricing tiers, comparison table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm&lt;/td&gt;
&lt;td&gt;&lt;a href="https://astro-fleet-olive.pages.dev" rel="noopener noreferrer"&gt;astro-fleet-olive.pages.dev&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Restaurant — warm gradients, serif type, dotted-leader menu&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Click through to the About and Contact pages — each site uses a different combination of the shared components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx create-astro-fleet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Indivar/astro-fleet.git
&lt;span class="nb"&gt;cd &lt;/span&gt;astro-fleet
bun &lt;span class="nb"&gt;install
&lt;/span&gt;bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Indivar/astro-fleet/blob/main/docs/getting-started.md" rel="noopener noreferrer"&gt;Getting Started Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Indivar/astro-fleet/blob/main/docs/components.md" rel="noopener noreferrer"&gt;Components Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Indivar/astro-fleet/blob/main/docs/design-tokens.md" rel="noopener noreferrer"&gt;Design Tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Indivar/astro-fleet/blob/main/docs/adding-a-cms.md" rel="noopener noreferrer"&gt;Adding a CMS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Indivar/astro-fleet/blob/main/docs/seo-recipes.md" rel="noopener noreferrer"&gt;SEO Recipes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Indivar/astro-fleet/blob/main/docs/ai-workflow.md" rel="noopener noreferrer"&gt;AI Workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Indivar/astro-fleet" rel="noopener noreferrer"&gt;github.com/Indivar/astro-fleet&lt;/a&gt; — MIT licensed, contributions welcome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We use this in production to run &lt;a href="https://www.vairi.com" rel="noopener noreferrer"&gt;vairi.com&lt;/a&gt;, &lt;a href="https://www.claspt.app" rel="noopener noreferrer"&gt;claspt.app&lt;/a&gt;, and &lt;a href="https://www.stakteck.com" rel="noopener noreferrer"&gt;stakteck.com&lt;/a&gt;. If you ship a site with Astro Fleet, open a PR adding it to the "Built with" section — we'd love to feature it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building an encrypted vault with Tauri + Rust — architecture decisions and trade-offs</title>
      <dc:creator>Varinder Singh</dc:creator>
      <pubDate>Thu, 02 Apr 2026 12:43:52 +0000</pubDate>
      <link>https://dev.to/varinder_singh_c541dcb059/building-an-encrypted-vault-with-tauri-rust-architecture-decisions-and-trade-offs-519p</link>
      <guid>https://dev.to/varinder_singh_c541dcb059/building-an-encrypted-vault-with-tauri-rust-architecture-decisions-and-trade-offs-519p</guid>
      <description>&lt;p&gt;I recently launched Claspt, a desktop app that combines markdown note-taking with AES-256 encrypted secret storage. This post covers the technical decisions behind it — what worked, what didn't, and what I'd do differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Tauri over Electron
&lt;/h2&gt;

&lt;p&gt;The numbers tell the story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bundle size: ~5MB vs ~200MB&lt;/li&gt;
&lt;li&gt;Memory at idle: ~40MB vs ~150MB&lt;/li&gt;
&lt;li&gt;Startup time: &amp;lt;1s vs 2-3s&lt;/li&gt;
&lt;li&gt;Backend: Rust (native speed) vs Node.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tauri 2.x gives you a Rust backend with a WebView frontend. The frontend is React + TypeScript + Tailwind. The backend handles everything performance-sensitive: encryption, file I/O, full-text search, Git operations.&lt;/p&gt;

&lt;p&gt;The trade-off: WebView rendering varies slightly across platforms (especially font rendering and scrollbar behavior). And the ecosystem is younger — fewer plugins, smaller community, more DIY.&lt;/p&gt;

&lt;p&gt;Worth it? Absolutely. Users notice the speed difference immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption Architecture
&lt;/h2&gt;

&lt;p&gt;The key hierarchy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Master password&lt;/strong&gt; → Argon2id (64MB memory, 3 iterations, 32-byte salt) → &lt;strong&gt;Master key&lt;/strong&gt; (256-bit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Master key&lt;/strong&gt; encrypts/decrypts individual secret blocks&lt;/li&gt;
&lt;li&gt;Each secret block: AES-256-GCM with unique 96-bit nonce&lt;/li&gt;
&lt;li&gt;Master key stored in &lt;code&gt;vault.key&lt;/code&gt; (encrypted with itself — bootstrapped from password)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why per-block encryption instead of encrypting the whole file?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown content stays plaintext → readable in any editor, searchable, git-diffable&lt;/li&gt;
&lt;li&gt;Only the sensitive parts are encrypted&lt;/li&gt;
&lt;li&gt;Each block has its own nonce → compromising one block doesn't help with others&lt;/li&gt;
&lt;li&gt;Labels stay visible for organization → &lt;code&gt;:::secret[AWS Access Key]&lt;/code&gt; is plaintext, the value inside is ciphertext&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Argon2id parameters (64MB, 3 iterations) are based on OWASP recommendations. On a modern machine, key derivation takes ~200ms — imperceptible on unlock, painful for brute force.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Engine: tantivy
&lt;/h2&gt;

&lt;p&gt;I considered three options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SQLite FTS5&lt;/strong&gt;: Simple, embedded, works everywhere. Used this on mobile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tantivy&lt;/strong&gt;: Rust-native, Lucene-like, field-level boosting. Used this on desktop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom inverted index&lt;/strong&gt;: Maximum control, maximum effort. Rejected.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;tantivy won for desktop because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field-level boosting: title (3x), tags (2x), secret labels (2x), content (1x)&lt;/li&gt;
&lt;li&gt;Sub-100ms search across thousands of documents&lt;/li&gt;
&lt;li&gt;Rust-native = no FFI overhead, compiles with the rest of the backend&lt;/li&gt;
&lt;li&gt;Incremental indexing on save&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Critical design decision: &lt;strong&gt;secret values are never indexed&lt;/strong&gt;. The search index only contains titles, tags, labels, and non-secret content. Even if someone extracts the search index files, no secret values are exposed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP Server
&lt;/h2&gt;

&lt;p&gt;This is the feature I'm most excited about. Claspt exposes a Model Context Protocol (MCP) server on localhost. AI tools like Claude Code, Cursor, and Windsurf can connect to it and use your vault as persistent memory.&lt;/p&gt;

&lt;p&gt;How it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP server runs on a configurable local port&lt;/li&gt;
&lt;li&gt;Two-tier token auth: notes-only token (read/write notes) and secrets token (can also read secrets)&lt;/li&gt;
&lt;li&gt;AI tools connect via the MCP protocol and get tools like &lt;code&gt;read_page&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt;, &lt;code&gt;create_page&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your AI assistant can store and retrieve information across sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use case: "Remember this deployment procedure for next time" → stored as a note in your vault, available in every future conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Markdown-First: Why .md Files, Not a Database
&lt;/h2&gt;

&lt;p&gt;The vault is literally a folder:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/Claspt/
  general/
    my-note.md
  credentials/
    aws-setup.md
  .securenotes/
    config.json
    vault.key
    index/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Each page is a standalone .md file with YAML frontmatter. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No vendor lock-in&lt;/strong&gt;: Your notes are readable without Claspt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git-friendly&lt;/strong&gt;: Every save auto-commits. Full version history for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup-friendly&lt;/strong&gt;: It's a folder. rsync, Time Machine, whatever you use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync-friendly&lt;/strong&gt;: Git remotes, Google Drive, Dropbox — anything that syncs folders works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off: no relational queries, no structured data beyond frontmatter. But for a personal vault, folder + search is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with @dnd-kit from day one&lt;/strong&gt;. HTML5 drag-and-drop doesn't work in Tauri's WebView. Wasted time debugging before switching to pointer events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design the config system earlier&lt;/strong&gt;. Settings grew organically and the config struct is now 30+ fields. Should have used a more modular approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the browser extension in parallel&lt;/strong&gt;. It's the most-requested feature and designing it after the desktop app means some architectural retrofitting.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Free forever on desktop. No account required.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://claspt.app" rel="noopener noreferrer"&gt;https://claspt.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Security model: &lt;a href="https://claspt.app/security/" rel="noopener noreferrer"&gt;https://claspt.app/security/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://docs.claspt.app" rel="noopener noreferrer"&gt;https://docs.claspt.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is Rust + TypeScript. If you're building with Tauri, happy to discuss architecture decisions in the comments.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>tauri</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built an encrypted vault where markdown notes and passwords live together</title>
      <dc:creator>Varinder Singh</dc:creator>
      <pubDate>Thu, 02 Apr 2026 10:21:55 +0000</pubDate>
      <link>https://dev.to/varinder_singh_c541dcb059/i-built-an-encrypted-vault-where-markdown-notes-and-passwords-live-together-32lo</link>
      <guid>https://dev.to/varinder_singh_c541dcb059/i-built-an-encrypted-vault-where-markdown-notes-and-passwords-live-together-32lo</guid>
      <description>&lt;h2&gt;
  
  
  Use for a longer-form technical post covering:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why Tauri over Electron (bundle size, memory, Rust backend)&lt;/li&gt;
&lt;li&gt;The encryption architecture (key hierarchy, per-block encryption, Argon2id parameters)&lt;/li&gt;
&lt;li&gt;How the MCP server works for AI tool integration&lt;/li&gt;
&lt;li&gt;The markdown-first approach&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Link back to &lt;a href="https://claspt.app" rel="noopener noreferrer"&gt;https://claspt.app&lt;/a&gt; and &lt;a href="https://claspt.app/security/" rel="noopener noreferrer"&gt;https://claspt.app/security/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
