<?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: Léo Guillaume (Dibodev)</title>
    <description>The latest articles on DEV Community by Léo Guillaume (Dibodev) (@dibodev).</description>
    <link>https://dev.to/dibodev</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%2F4083948%2F811962fa-9a33-43e6-b381-b1d09afe1005.jpg</url>
      <title>DEV Community: Léo Guillaume (Dibodev)</title>
      <link>https://dev.to/dibodev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dibodev"/>
    <language>en</language>
    <item>
      <title>One Vue app, three homes: desktop with Tauri, Android with Capacitor, and the web</title>
      <dc:creator>Léo Guillaume (Dibodev)</dc:creator>
      <pubDate>Tue, 18 Aug 2026 23:23:27 +0000</pubDate>
      <link>https://dev.to/dibodev/one-vue-app-three-homes-desktop-with-tauri-android-with-capacitor-and-the-web-4eoe</link>
      <guid>https://dev.to/dibodev/one-vue-app-three-homes-desktop-with-tauri-android-with-capacitor-and-the-web-4eoe</guid>
      <description>&lt;p&gt;I wanted a Reddit client that lived on my desktop &lt;em&gt;and&lt;/em&gt; my phone — without maintaining two separate apps. So I built one Vue app and gave it three homes: &lt;strong&gt;Tauri&lt;/strong&gt; (Rust) wraps it as a desktop app for Windows/macOS/Linux, &lt;strong&gt;Capacitor&lt;/strong&gt; wraps it for Android, and it still runs as a plain web app. One UI, one set of Reddit API calls, one OAuth flow — three targets.&lt;/p&gt;

&lt;p&gt;It works. But "just wrap your web app" hides a few things. Here's the reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A single &lt;strong&gt;Vue.js&lt;/strong&gt; frontend: the whole UI, the Reddit API calls, the OAuth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tauri&lt;/strong&gt; for desktop — the webview is the OS's native one, the backend is Rust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacitor&lt;/strong&gt; for Android — desktop got Tauri, Android got Capacitor. Use the wrapper that's mature for each target instead of forcing one tool everywhere.&lt;/li&gt;
&lt;li&gt;Plain &lt;strong&gt;web&lt;/strong&gt; for anyone who just wants a URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gotcha 1: you can't cross-compile
&lt;/h2&gt;

&lt;p&gt;You cannot build a Windows &lt;code&gt;.msi&lt;/code&gt; on your Mac. Tauri leans on each platform's native toolchain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.msi&lt;/code&gt; / &lt;code&gt;.exe&lt;/code&gt; → built on Windows&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.dmg&lt;/code&gt; / &lt;code&gt;.app&lt;/code&gt; → built on macOS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.deb&lt;/code&gt; / &lt;code&gt;.AppImage&lt;/code&gt; → built on Linux&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Ship for all platforms" really means a CI matrix with one runner per OS. Plan for it early — it shapes your whole release pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 2: WebView2 on older Windows
&lt;/h2&gt;

&lt;p&gt;Tauri doesn't ship a browser engine; it uses the system webview. Windows 11 has WebView2; Windows 7–10 might not — and a missing webview means users open the app to a blank white window. Bundle the WebView2 bootstrapper in your installer so it self-installs. Tiny detail, terrible first impression if you miss it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 3: OAuth redirect is the fiddly part
&lt;/h2&gt;

&lt;p&gt;In a browser, OAuth is easy: Reddit redirects to your callback URL, you read the code, done. In a &lt;em&gt;packaged&lt;/em&gt; app there's no web server at that URL. So you register a custom scheme / deep link, let the OS hand the redirect back to the app, and pull the &lt;code&gt;code&lt;/code&gt; out of the incoming URL — on desktop and Android, each with its own quirks. Easily the most time-consuming part, and the bit tutorials skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 4: a default Rust release binary is chunky
&lt;/h2&gt;

&lt;p&gt;A handful of &lt;code&gt;Cargo.toml&lt;/code&gt; flags make a real dent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[profile.release]&lt;/span&gt;
&lt;span class="py"&gt;panic&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"abort"&lt;/span&gt;      &lt;span class="c"&gt;# drop the unwinding machinery&lt;/span&gt;
&lt;span class="py"&gt;codegen-units&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="c"&gt;# compile as one unit so the optimizer can do more&lt;/span&gt;
&lt;span class="py"&gt;lto&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;           &lt;span class="c"&gt;# link-time optimization&lt;/span&gt;
&lt;span class="py"&gt;opt-level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"s"&lt;/span&gt;      &lt;span class="c"&gt;# optimize for size&lt;/span&gt;
&lt;span class="py"&gt;strip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;         &lt;span class="c"&gt;# strip symbols from the binary&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free size win, no code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was it worth it?
&lt;/h2&gt;

&lt;p&gt;Yes. One Vue codebase, native apps on desktop and Android, plus a web version — without three projects to keep in sync. If you're a web dev eyeing desktop/mobile, Vue + Tauri + Capacitor is a very reasonable way in — just budget time for the packaging and OAuth reality, not the happy-path demo.&lt;/p&gt;

&lt;p&gt;I do this kind of cross-platform / custom-tooling work as a freelance dev at &lt;a href="https://dibodev.fr" rel="noopener noreferrer"&gt;dibodev.fr&lt;/a&gt;. Happy to get into the OAuth deep-linking or the CI matrix in the comments.&lt;/p&gt;

</description>
      <category>tauri</category>
      <category>rust</category>
      <category>vue</category>
      <category>webdev</category>
    </item>
    <item>
      <title>My static Nuxt blog publishes itself: drafts, scheduled "drip", and auto-rebuilds</title>
      <dc:creator>Léo Guillaume (Dibodev)</dc:creator>
      <pubDate>Tue, 18 Aug 2026 22:45:55 +0000</pubDate>
      <link>https://dev.to/dibodev/my-static-nuxt-blog-publishes-itself-drafts-scheduled-drip-and-auto-rebuilds-4hmi</link>
      <guid>https://dev.to/dibodev/my-static-nuxt-blog-publishes-itself-drafts-scheduled-drip-and-auto-rebuilds-4hmi</guid>
      <description>&lt;p&gt;I run my freelance site as a fully static (SSG) Nuxt site. It's fast, cheap to host, and boring in the good way. But static sites are genuinely bad at one thing: publishing an article next Tuesday at 9am. There's no server rendering each request, so "schedule this post" doesn't exist — something has to rebuild the site at the right moment.&lt;/p&gt;

&lt;p&gt;I also write in bursts. Some evenings I'll draft two or three articles at once, and I don't want them all going live the same day. So I bolted a small publishing engine onto my existing stack: drafts, a scheduled queue (a "drip"), and automatic rebuilds. Here's how the pieces fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Nuxt 4, prerendered (SSG) for every public page&lt;/li&gt;
&lt;li&gt;Storyblok as the headless CMS — the source of truth for content&lt;/li&gt;
&lt;li&gt;A Nitro server (the same Nuxt app) on a small VPS under PM2 — the always-on part&lt;/li&gt;
&lt;li&gt;GitHub Actions for the deploy/rebuild&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing rests on one realization: on an SSG site, "publish" is two actions, not one — (1) put the content in the CMS, and (2) rebuild the static site so it actually shows up. Scheduling is just doing both later, automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Drafts: write now, decide later
&lt;/h2&gt;

&lt;p&gt;The dashboard has a markdown editor. Drafts are saved server-side (Nitro's storage layer), each with a status (draft / scheduled / published) and a publish date. Nothing touches the live site yet — a draft is just a row waiting for its moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The queue and the "drip"
&lt;/h2&gt;

&lt;p&gt;The fun part. A Nitro scheduled task runs every hour and asks one question: &lt;em&gt;is anything due?&lt;/em&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="c1"&gt;// nuxt.config.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;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;nitro&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tasks&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;scheduledTasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5 * * * *&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articles:process-queue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// every hour, at :05&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/tasks/articles/process-queue.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;defineTask&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;meta&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;articles:process-queue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&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;due&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;getDueScheduledArticles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// publishDate &amp;lt;= now&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nothing due&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for &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;article&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;publishToStoryblok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// markdown -&amp;gt; richtext -&amp;gt; CMS&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;triggerSiteRebuild&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// ONE rebuild for the whole batch&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`published &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;due&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;That's the entire scheduler. No queue service, no cron box, no extra infra — just a task the Nitro server already knows how to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Markdown → CMS → rebuild
&lt;/h2&gt;

&lt;p&gt;When a post is due, it's converted from markdown to Storyblok's richtext format, pushed to the CMS, and then — the non-negotiable step on a static site — the site is rebuilt. No rebuild, no article. I trigger it with a &lt;code&gt;workflow_dispatch&lt;/code&gt; call to my deploy workflow:&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;triggerSiteRebuild&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$fetch&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://api.github.com/repos/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;/actions/workflows/deploy.yml/dispatches&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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;REPO_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// needs `actions: write`&lt;/span&gt;
        &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/vnd.github+json&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Three gotchas I hit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;There's still a server.&lt;/strong&gt; The scheduled task only fires if something is actually running. It's easy to forget an SSG site has no always-on backend by default — the Nitro server under PM2 exists purely so the scheduler (and the admin) stay alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One rebuild per batch, not per article.&lt;/strong&gt; If three posts are due in the same run, publish all three &lt;em&gt;then&lt;/em&gt; rebuild once. Otherwise you kick off three deploys back to back for nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timezones.&lt;/strong&gt; "9am" means nothing until you pin the timezone. Ask me how I know.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The payoff
&lt;/h2&gt;

&lt;p&gt;Now I can draft on a Sunday, spread posts across the next two weeks, and close the laptop. They go live on their own, the site rebuilds itself, and I stop babysitting a "publish" button.&lt;/p&gt;

&lt;p&gt;It powers the blog on my freelance site, &lt;a href="https://dibodev.fr" rel="noopener noreferrer"&gt;dibodev.fr&lt;/a&gt; — building this kind of custom tooling (and business apps / SaaS) for small companies is basically my day job.&lt;/p&gt;

&lt;p&gt;Happy to dig into the Nitro task or the rebuild trigger in the comments.&lt;/p&gt;

</description>
      <category>nuxt</category>
      <category>vue</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
