<?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>Forem: Matthias Andrasch</title>
    <description>The latest articles on Forem by Matthias Andrasch (@mandrasch).</description>
    <link>https://forem.com/mandrasch</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%2F775550%2F140349fc-ec5c-40bd-b3b1-f68877defc52.jpeg</url>
      <title>Forem: Matthias Andrasch</title>
      <link>https://forem.com/mandrasch</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mandrasch"/>
    <language>en</language>
    <item>
      <title>Deploy and run Nuxt SSR websites via Laravel Forge (and Hetzner)</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Mon, 08 Dec 2025 11:05:22 +0000</pubDate>
      <link>https://forem.com/mandrasch/host-nuxt-ssr-websites-via-laravel-forge-5d16</link>
      <guid>https://forem.com/mandrasch/host-nuxt-ssr-websites-via-laravel-forge-5d16</guid>
      <description>&lt;p&gt;Forge is a server management service made by the creator of the well known PHP framework laravel. But Forge is meant to be used for more than PHP applications.&lt;/p&gt;

&lt;p&gt;Since the &lt;a href="https://laravel.com/blog/everything-you-need-to-know-about-the-new-forge-laravel-vps" rel="noopener noreferrer"&gt;new platform release&lt;/a&gt;, Forge explicitly supports NodeJS SSR hosting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nuxt and Next.js integrations: Forge offers first-class support for modern JavaScript frameworks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is server side rendering (SSR)?
&lt;/h2&gt;

&lt;p&gt;Server Side Rendering isn't new for developers familiar with PHP, since PHP always runs server side first. But for JavaScript developers, who have been creating Single Page Applications (SPA) or static projects which rely on JavaScript in the browser, SSR can be a great addition: Your code will be first executed on the server, therefore you can call authenticated APIs in secrecy - or deliver ready-made HTML to be consumed by non-javascript SEO bots. See &lt;a href="https://nuxt.com/docs/4.x/guide/concepts/rendering" rel="noopener noreferrer"&gt;Nuxt.js Rendering modes&lt;/a&gt; for more information about server side, client side and hydration techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's try Nuxt.js on Forge
&lt;/h2&gt;

&lt;p&gt;To get started on Forge, you need to invest $12 per month for the Hobby tier. This will enable you to add: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unlimited Laravel VPS servers&lt;/li&gt;
&lt;li&gt;1 external server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you plan to use more than one external server, e.g. from Hetzner, you need to pay a bit more: $19 / month for the growth plan. See &lt;a href="https://forge.laravel.com/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Beware: As it is common for these server management tools, each added server has its own additional usage fees. 💸&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel VPS servers
&lt;/h3&gt;

&lt;p&gt;Since the &lt;a href="https://laravel.com/blog/everything-you-need-to-know-about-the-new-forge-laravel-vps" rel="noopener noreferrer"&gt;new Forge feature launch&lt;/a&gt;, it is possible to use VPS servers directly managed by Forge:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of jumping between Forge and your cloud provider's dashboard to manage servers, billing, and configuration, everything lives in one place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Prices start at $6 / month, see &lt;a href="https://forge.laravel.com/docs/servers/laravel-vps#pricing" rel="noopener noreferrer"&gt;Laravel VPS server pricing&lt;/a&gt;.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Ffglbi8f1wbnm2w3g2ffd.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.amazonaws.com%2Fuploads%2Farticles%2Ffglbi8f1wbnm2w3g2ffd.png" alt=" " width="800" height="699"&gt;&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fswiw50oojxgu70ar5r4m.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.amazonaws.com%2Fuploads%2Farticles%2Fswiw50oojxgu70ar5r4m.png" alt=" " width="800" height="860"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following regions are currently supported:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fmx1sl2m2ibvteno5kaey.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.amazonaws.com%2Fuploads%2Farticles%2Fmx1sl2m2ibvteno5kaey.png" alt=" " width="580" height="816"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  External VPS providers
&lt;/h3&gt;

&lt;p&gt;You can also add external providers, e.g. Hetzner and connect them via API key:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F7s8r0u27zr0kegzh6oxy.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.amazonaws.com%2Fuploads%2Farticles%2F7s8r0u27zr0kegzh6oxy.png" alt=" " width="800" height="721"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then use the &lt;a href="https://www.hetzner.com/de/cloud" rel="noopener noreferrer"&gt;Hetzner Cloud VPS servers&lt;/a&gt; or dedicated ones:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fead0be299mtid4ph510n.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.amazonaws.com%2Fuploads%2Farticles%2Fead0be299mtid4ph510n.png" alt=" " width="800" height="711"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beware: The pricing shown here differs a bit from &lt;a href="https://www.hetzner.com/de/cloud" rel="noopener noreferrer"&gt;the Hetzner pricing&lt;/a&gt; I know. Cheapest server is currently capped at 4,15€/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add your first server
&lt;/h2&gt;

&lt;p&gt;For my example here, I'll use the cheapest Hetzner VPS. For private network, just create a new one. &lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fghnbm4bqh1ovhspl0bij.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.amazonaws.com%2Fuploads%2Farticles%2Fghnbm4bqh1ovhspl0bij.png" alt=" " width="800" height="708"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use the app server option, there are other ones available:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F2od4hywvcxkufj798qwm.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.amazonaws.com%2Fuploads%2Farticles%2F2od4hywvcxkufj798qwm.png" alt=" " width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fehq1uah11qwx154cgncl.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.amazonaws.com%2Fuploads%2Farticles%2Fehq1uah11qwx154cgncl.png" alt=" " width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the advanced section, the defaults currently are the following:&lt;/p&gt;

&lt;p&gt;After clicking "create server", Forge will now connect via SSH to the new server and install the necessary linux packages as well as creating the needed configuration:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fqqg6hj6u9zogw1he6gkr.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.amazonaws.com%2Fuploads%2Farticles%2Fqqg6hj6u9zogw1he6gkr.png" alt=" " width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the nuxt sample website
&lt;/h2&gt;

&lt;p&gt;Get started by creating a sample nuxt app,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create nuxt@latest nuxt-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the minimal setup option:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fwj6pkvezxau6v11bz7cl.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.amazonaws.com%2Fuploads%2Farticles%2Fwj6pkvezxau6v11bz7cl.png" alt=" " width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To check out the Server Side Rendering (SSR) features, we add a new SSR route which fetches data externally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/api/cat-facts&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="c1"&gt;// we could also use authenticated APIs here, secrets won't be shown to the client&lt;/span&gt;
  &lt;span class="k"&gt;return&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://catfact.ninja/fact&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/pages/index.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&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;useFetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/cat-fact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"p-10 max-w-2xl mx-auto"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl mb-4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;🐈 Random cat fact&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fact&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;NuxtWelcome&lt;/code&gt; with &lt;code&gt;NuxtPage&lt;/code&gt; in &lt;code&gt;app/app.vue&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;NuxtLayout&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;NuxtRouteAnnouncer&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;NuxtPage&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/NuxtLayout&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full code: &lt;a href="https://github.com/mandrasch/nuxt-example" rel="noopener noreferrer"&gt;https://github.com/mandrasch/nuxt-example&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy / host it via Forge
&lt;/h2&gt;

&lt;p&gt;On your newly created server, we now select "Create site" in the Sites tab and select "Nuxt.js" with "Nuxt.js server":&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fkkg0ygc4eb54dgnz240i.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.amazonaws.com%2Fuploads%2Farticles%2Fkkg0ygc4eb54dgnz240i.png" alt=" " width="800" height="1308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The defaults for advanced settings are the following:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F6jb3f1rjk5mr5q2zhc7p.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.amazonaws.com%2Fuploads%2Farticles%2F6jb3f1rjk5mr5q2zhc7p.png" alt=" " width="800" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  First deploy
&lt;/h3&gt;

&lt;p&gt;We can now start our first deployment after connecting our GitHub account to Forge:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F0almshclnyybgpunhwht.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.amazonaws.com%2Fuploads%2Farticles%2F0almshclnyybgpunhwht.png" alt=" " width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The site is then launched via &lt;a href="https://pm2.keymetrics.io/" rel="noopener noreferrer"&gt;pm2&lt;/a&gt;:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F5ihhtap5if8tjzqq3ka3.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.amazonaws.com%2Fuploads%2Farticles%2F5ihhtap5if8tjzqq3ka3.png" alt=" " width="800" height="668"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Et voilà, our SSR website is online:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fzwt8vmbif2g57uoiltpq.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.amazonaws.com%2Fuploads%2Farticles%2Fzwt8vmbif2g57uoiltpq.png" alt=" " width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adjust deploy script
&lt;/h3&gt;

&lt;p&gt;The defaults for the deploy script can be adjusted:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Faounlouzcwnrvriw5tpb.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.amazonaws.com%2Fuploads%2Farticles%2Faounlouzcwnrvriw5tpb.png" alt=" " width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set environment variables
&lt;/h3&gt;

&lt;p&gt;It is also possible to set env vars via the Forge interface:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fow4093w7mzcy05zle5sa.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.amazonaws.com%2Fuploads%2Farticles%2Fow4093w7mzcy05zle5sa.png" alt=" " width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Background processes and scheduler
&lt;/h3&gt;

&lt;p&gt;There are also two additional options: Scheduler and Background processes&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fm9agz73n7oz3lxpbh4nh.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.amazonaws.com%2Fuploads%2Farticles%2Fm9agz73n7oz3lxpbh4nh.png" alt=" " width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F2v8k4xnb3jjj628by690.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.amazonaws.com%2Fuploads%2Farticles%2F2v8k4xnb3jjj628by690.png" alt=" " width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Commands
&lt;/h3&gt;

&lt;p&gt;Execute commands remotely:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Faewq7tlgday7w0nbdztk.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.amazonaws.com%2Fuploads%2Farticles%2Faewq7tlgday7w0nbdztk.png" alt=" " width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Heartbeat and Logs
&lt;/h3&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fzcgipy6j7gwjwr3ijygl.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.amazonaws.com%2Fuploads%2Farticles%2Fzcgipy6j7gwjwr3ijygl.png" alt=" " width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fyirk7p50r63zxf84ouu8.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.amazonaws.com%2Fuploads%2Farticles%2Fyirk7p50r63zxf84ouu8.png" alt=" " width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Other options
&lt;/h3&gt;

&lt;p&gt;There are some more options available, e.g. Deploy hook, Healthchecks. You can also decide wheter you want to enable push to deploy from GitHub or trigger it manual.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redis installed by default
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Laravel Forge automatically installs both Memcached and Redis™ when provisioning App Servers or Cache Servers. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;See &lt;a href="https://forge.laravel.com/docs/resources/caches" rel="noopener noreferrer"&gt;https://forge.laravel.com/docs/resources/caches&lt;/a&gt; for more information&lt;/p&gt;

&lt;p&gt;You can adjust the Redis password in the Server settings:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fgtzyk6t1rfa5g42ylv2r.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.amazonaws.com%2Fuploads%2Farticles%2Fgtzyk6t1rfa5g42ylv2r.png" alt=" " width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is handy for caching, see e.g. &lt;a href="https://nuxt-multi-cache.dulnan.net/" rel="noopener noreferrer"&gt;https://nuxt-multi-cache.dulnan.net/&lt;/a&gt; or similiar extensions for Nuxt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Forge did an awesome job integrating Nuxt.js in their polished and clean server management interface, kudos! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is your experience with Forge?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next?
&lt;/h3&gt;

&lt;p&gt;It would be great if there would be out of the box support for &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SvelteKit SSR via &lt;a href="https://svelte.dev/docs/kit/adapter-node" rel="noopener noreferrer"&gt;adapter-node&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Astros &lt;a href="https://docs.astro.build/en/guides/on-demand-rendering/" rel="noopener noreferrer"&gt;On-Demand-Rendering&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;in future! &lt;/p&gt;

&lt;p&gt;In the meantime, it might be possible to run SvelteKit or Astro with SSR with some hacking / customization on Forge, see e.g. &lt;a href="https://forge.laravel.com/docs/servers/nginx-templates" rel="noopener noreferrer"&gt;https://forge.laravel.com/docs/servers/nginx-templates&lt;/a&gt;. But I haven't tested yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Similiar services
&lt;/h3&gt;

&lt;p&gt;Similiar server management services are &lt;a href="https://ploi.io/" rel="noopener noreferrer"&gt;ploi.io&lt;/a&gt;, &lt;a href="https://cleavr.io/" rel="noopener noreferrer"&gt;cleavr&lt;/a&gt; or &lt;a href="https://coolify.io/" rel="noopener noreferrer"&gt;Coolify (selfhosting possible)&lt;/a&gt;. Another platform I recently became aware of is  &lt;a href="https://sliplane.io/en-us" rel="noopener noreferrer"&gt;Sliplane Docker Hosting&lt;/a&gt;, a company from Berlin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read more:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/mandrasch/rich-harris-explains-why-sveltekit-pushes-for-server-side-rendering-and-against-spa-5flj"&gt;Rich Harris explains why SvelteKit pushes for Server Side Rendering (and against SPA / CSR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.offerzen.com/blog/taylor-otwell-laravel-forge-story" rel="noopener noreferrer"&gt;The story behind Forge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nuxt.com/docs/4.x/guide/concepts/rendering" rel="noopener noreferrer"&gt;Nuxt v4 rendering modes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>nuxt</category>
      <category>node</category>
      <category>ssr</category>
    </item>
    <item>
      <title>Simple Coolify example with Docker Compose + Github</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Sun, 09 Mar 2025 11:34:41 +0000</pubDate>
      <link>https://forem.com/mandrasch/simple-coolify-example-with-docker-compose-github-deployments-53m</link>
      <guid>https://forem.com/mandrasch/simple-coolify-example-with-docker-compose-github-deployments-53m</guid>
      <description>&lt;p&gt;I recently learned a bit more about Docker Compose and wanted to apply my new knowledge to Coolify. The first goal is to setup a simple webserver with nginx to deploy a "Hello World" HTML webpage. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Coolify?
&lt;/h2&gt;

&lt;p&gt;Coolify is an "open-source &amp;amp; self-hostable Heroku / Netlify / Vercel alternative". It is developed by &lt;a href="https://twitter.com/heyandras" rel="noopener noreferrer"&gt;Andras Bacsai&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The advantage of Coolify compared to other (awesome) server management tools like ploi.io, Cleavr or Laravel Forge is that you can install it directly on your VPS without additional subscription costs. This is especially great for low budget or small hobby projects.&lt;/li&gt;
&lt;li&gt;Another advantage is that you can connect your GitHub account and directly deploy repositories (like you would do on Vercel, etc).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My demo specs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPS: &lt;a href="https://www.hetzner.com/cloud" rel="noopener noreferrer"&gt;Hetzner Cloud CPX11 (AMD)&lt;/a&gt; - €4,35/month&lt;/li&gt;
&lt;li&gt;Coolify v4.0.0-beta.390&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You could also choose ARM as &lt;a href="https://github.com/coollabsio/coolify/issues/2088#issuecomment-2082265491" rel="noopener noreferrer"&gt;a faster choice&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Install selfhosted Coolify on Hetzner VPS
&lt;/h2&gt;

&lt;p&gt;Just create a new &lt;a href="https://www.hetzner.com/cloud/" rel="noopener noreferrer"&gt;CPX11 cloud server&lt;/a&gt; on Hetzner, connect via SSH (&lt;code&gt;ssh root@your-server-ip&lt;/code&gt;) and run the command from &lt;a href="https://coolify.io/self-hosted:" rel="noopener noreferrer"&gt;https://coolify.io/self-hosted:&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://cdn.coollabs.io/coolify/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's really it. After installation, you can create your admin user via &lt;code&gt;http://your-vps-server-ip:8000/&lt;/code&gt;. See this slightly older YouTube video for full example: &lt;a href="https://www.youtube.com/watch?v=Jg6SWqyvYys" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=Jg6SWqyvYys&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Add swap space to prevent "out of memory"
&lt;/h3&gt;

&lt;p&gt;Sometimes I ran into "502 Bad Gateway" and 200% CPU load after hitting "Deploy" - while I had other (PHP + MySQL) resources running on my Coolify instance. I asked the Coolify Developer on GitHub and he gave me &lt;a href="https://github.com/coollabsio/coolify/issues/2088" rel="noopener noreferrer"&gt;multiple suggestions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I decided to try the swap option - Swap is &lt;strong&gt;disabled&lt;/strong&gt; by default on Hetzner VPS (see: &lt;a href="https://www.reddit.com/r/hetzner/comments/125hqf4/why_comes_the_default_linux_image_without_swap/" rel="noopener noreferrer"&gt;reddit discussion&lt;/a&gt;). I followed this guide (for ubuntu) and added 6GB swap space (since I only had 17G left on my small VPS) &lt;a href="https://www.digitalocean.com/community/tutorial-collections/how-to-add-swap-space" rel="noopener noreferrer"&gt;https://www.digitalocean.com/community/tutorial-collections/how-to-add-swap-space&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple Docker Compose example
&lt;/h2&gt;

&lt;p&gt;The first goal is to setup a simple webserver with nginx to deploy a "Hello World" HTML webpage. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create a new resource in Coolify
&lt;/h3&gt;

&lt;p&gt;Let's get the party started by creating a new project:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F10wj2ng5ojb1zllgq4yh.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.amazonaws.com%2Fuploads%2Farticles%2F10wj2ng5ojb1zllgq4yh.png" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select production:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F7kc05dvkfi8pp61vog2k.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.amazonaws.com%2Fuploads%2Farticles%2F7kc05dvkfi8pp61vog2k.png" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a new resource:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fd5de4cmr9zpmd66ae1h0.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.amazonaws.com%2Fuploads%2Farticles%2Fd5de4cmr9zpmd66ae1h0.png" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next step, it is important to use a Git Based approach. Don't get confused with the Docker options below.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fqsqhivzmoi47qz8iu4i8.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.amazonaws.com%2Fuploads%2Farticles%2Fqsqhivzmoi47qz8iu4i8.png" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll use the option "public repository" for now, but the cool thing is that you can also use private GitHub repositories.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F3c62ktaxv7gzkhnw0cos.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.amazonaws.com%2Fuploads%2Farticles%2F3c62ktaxv7gzkhnw0cos.png" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My demo repository: &lt;a href="https://github.com/mandrasch/coolify-docker-compose-simple-example" rel="noopener noreferrer"&gt;https://github.com/mandrasch/coolify-docker-compose-simple-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can switch to Docker Compose:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fthc10obqjnd5d45o0bvg.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.amazonaws.com%2Fuploads%2Farticles%2Fthc10obqjnd5d45o0bvg.png" width="800" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After selecting Docker Compose as Build Pack option, you can configure the location of the Docker Compose file. &lt;/p&gt;

&lt;p&gt;I decided to use &lt;code&gt;/docker-compose.production.yaml&lt;/code&gt; in my example:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fu7p3y529vg3etybek63s.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.amazonaws.com%2Fuploads%2Farticles%2Fu7p3y529vg3etybek63s.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  docker-compose.production.yaml
&lt;/h3&gt;

&lt;p&gt;My compose file has the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Normally, you would publish and expose ports like this in the docker compose file - don't do this on Coolify!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:80"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise you will get this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error response from daemon: driver failed programming external connectivity on endpoint web-XXXXXXXXXXX-XXXXXXXXXXX &lt;span class="o"&gt;(&lt;/span&gt;XXXXXXX&lt;span class="o"&gt;)&lt;/span&gt;: 

Bind &lt;span class="k"&gt;for &lt;/span&gt;0.0.0.0:8080 failed: port is already allocated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coolify handles this on its own, just leave it out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile.prod
&lt;/h3&gt;

&lt;p&gt;The Dockerfile defines how the Docker image will be built. The built  image is then used to launch Docker containers. We simply use an &lt;a href="https://hub.docker.com/_/nginx" rel="noopener noreferrer"&gt;nginx alpine linux image from DockerHub&lt;/a&gt; as starting point.&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;# Dockerfile.prod&lt;/span&gt;
&lt;span class="c"&gt;# Use the official NGINX image&lt;/span&gt;
FROM nginx:alpine

&lt;span class="c"&gt;# Copy the HTML files into the default NGINX public directory&lt;/span&gt;
COPY ./web /usr/share/nginx/html

&lt;span class="c"&gt;# Expose port 80 (the default port NGINX listens on)&lt;/span&gt;
EXPOSE 80

&lt;span class="c"&gt;# Start NGINX (this is the default behavior, no CMD or ENTRYPOINT needed)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  web/
&lt;/h3&gt;

&lt;p&gt;In the web/-directory, we just have a simple &lt;code&gt;index.html&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Hello Coolify!&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Verdana&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Geneva&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tahoma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello Coolify!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a temporary domain
&lt;/h3&gt;

&lt;p&gt;Coolify provides temporary domains, just create it here:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fzvlgxklioq6kodept3oi.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.amazonaws.com%2Fuploads%2Farticles%2Fzvlgxklioq6kodept3oi.png" width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will give you a URL like &lt;code&gt;http://XXXXXXXXXX.1.2.3.4.sslip.io&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hit deploy
&lt;/h3&gt;

&lt;p&gt;Click the Deploy button on the upper right and wait until the status changes to "Running".&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Frrrydxxf6f9vr16fyqct.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.amazonaws.com%2Fuploads%2Farticles%2Frrrydxxf6f9vr16fyqct.png" alt="Image description" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Open it
&lt;/h3&gt;

&lt;p&gt;We can now open the webpage in our web browser (this will use exposed port 80 by default):&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Facu10jah6jxkm2p6nwsn.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.amazonaws.com%2Fuploads%2Farticles%2Facu10jah6jxkm2p6nwsn.png" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice work! 🥳 &lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Working with mounts ⚠️
&lt;/h2&gt;

&lt;p&gt;If you want to mount a config file or directory, you need to select the checkbox "Preserve Repository During Deployment"&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fmcqspdspndz08ifvgk39.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.amazonaws.com%2Fuploads%2Farticles%2Fmcqspdspndz08ifvgk39.png" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Otherwise something like that won't work, the file &lt;code&gt;default.conf&lt;/code&gt; from your git repository is not found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./default.conf:/etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error response from daemon: failed to create task &lt;span class="k"&gt;for &lt;/span&gt;container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting &lt;span class="s2"&gt;"/data/coolify/applications/x0cg4ggoo0ckg4ggk4ggkssw/default.conf"&lt;/span&gt; to rootfs at &lt;span class="s2"&gt;"/etc/nginx/conf.d/default.conf"&lt;/span&gt;: create mountpoint &lt;span class="k"&gt;for&lt;/span&gt; /etc/nginx/conf.d/default.conf mount: cannot create subdirectories &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"/var/lib/docker/overlay2/941c13a1e70cc6381b5d34a6cb24933dc542ebd9bdeb2457cfc47bd89dcbe73f/merged/etc/nginx/conf.d/default.conf"&lt;/span&gt;: not a directory: unknown: Are you trying to mount a directory onto a file &lt;span class="o"&gt;(&lt;/span&gt;or vice-versa&lt;span class="o"&gt;)&lt;/span&gt;? Check &lt;span class="k"&gt;if &lt;/span&gt;the specified host path exists and is the expected &lt;span class="nb"&gt;type
exit &lt;/span&gt;status 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the preserve checkbox ticked, everything works as expected.&lt;/p&gt;

&lt;p&gt;As alternative to mounts, you can also copy files or directories in your Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM nginx:alpine AS BASE

COPY ./default.conf /etc/nginx/conf.d/default.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next tutorials
&lt;/h2&gt;

&lt;p&gt;In the next tutorials I want to step up our game a little bit. My personal goal is to deploy and host CraftCMS websites (PHP + MySQL database + redis). Therefore we need at least three services in our Docker Compose config file. You could also deploy a headless CMS + a decoupled frontend like NuxtJS / SvelteKit / NextJS with that approach. See &lt;a href="https://coolify.io/docs/knowledge-base/docker/compose" rel="noopener noreferrer"&gt;https://coolify.io/docs/knowledge-base/docker/compose&lt;/a&gt; for more information about Docker Compose on Coolify.&lt;/p&gt;

&lt;p&gt;UPDATE: Samuel Reichör published the tutorial &lt;a href="https://samuelreichor.at/blogs/craft-coolify" rel="noopener noreferrer"&gt;Deploying a Craft CMS Website with Coolify to a 4€ VPS&lt;/a&gt; 🎉&lt;/p&gt;

&lt;p&gt;Other examples and tutorials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://strapi.io/blog/how-to-deploy-strapi-application-on-coolify-using-docker-compose" rel="noopener noreferrer"&gt;How to deploy you Strapi application on Coolify&lt;/a&gt; - strapi.io&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PS: If you just want to deploy a single service, you can also use Nixpacks as very simple alternative: &lt;a href="https://dev.to/mandrasch/deploy-sveltekit-with-ssr-on-coolify-hetzner-vps-24c5"&gt;https://dev.to/mandrasch/deploy-sveltekit-with-ssr-on-coolify-hetzner-vps-24c5&lt;/a&gt;&lt;/p&gt;

</description>
      <category>coolify</category>
      <category>devops</category>
      <category>dockercompose</category>
      <category>docker</category>
    </item>
    <item>
      <title>DDEV and PhpStorm's NodeJS Remote Interpreter for ESLint, Prettier, Tailwind, etc.</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Thu, 27 Feb 2025 13:20:09 +0000</pubDate>
      <link>https://forem.com/mandrasch/ddev-and-phpstorms-nodejs-remote-interpreter-for-eslint-prettier-tailwind-etc-1lm6</link>
      <guid>https://forem.com/mandrasch/ddev-and-phpstorms-nodejs-remote-interpreter-for-eslint-prettier-tailwind-etc-1lm6</guid>
      <description>&lt;p&gt;PhpStorm is great, but there is only one small limitation  while using it with DDEV. Since PhpStorm can't connect to the DDEV's web container of the running project, there is currently no way to just automagically use the defined  &lt;a href="https://ddev.readthedocs.io/en/stable/users/configuration/config/#nodejs_version" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs_version&lt;/code&gt;&lt;/a&gt; from &lt;code&gt;.ddev/config.yaml&lt;/code&gt; for ESlint, etc. &lt;/p&gt;

&lt;p&gt;If you use the (awesome) &lt;a href="https://plugins.jetbrains.com/plugin/18813-ddev-integration" rel="noopener noreferrer"&gt;DDEV Integration Plugin&lt;/a&gt; and activate ESLint, Prettier, etc. - it will even crash your DDEV project as soon as you edit a JS file. This is because the DDEV integration plugin currently sets the Remote Node.js Interpreter to this docker-compose path:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F5f5181no2qk6tubflb79.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.amazonaws.com%2Fuploads%2Farticles%2F5f5181no2qk6tubflb79.png" alt="Image description" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This leads to PhpStorm trying to launch another DDEV web container instead of connecting to the existing one to execute &lt;code&gt;npx eslint&lt;/code&gt; or similiar commands for prettier, etc. This results in an unhealthy DDEV project (which needs to be restarted via &lt;code&gt;ddev restart&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;You can check these issues out for more details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/php-perfect/ddev-intellij-plugin/issues/408" rel="noopener noreferrer"&gt;https://github.com/php-perfect/ddev-intellij-plugin/issues/408&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtrack.jetbrains.com/issue/WEB-71717" rel="noopener noreferrer"&gt;https://youtrack.jetbrains.com/issue/WEB-71717&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news: A workaround is quite simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workaround: Use a remote Docker image
&lt;/h2&gt;

&lt;p&gt;Just use the official &lt;a href="https://hub.docker.com/_/node" rel="noopener noreferrer"&gt;node Docker image&lt;/a&gt; with the same version as your DDEV project in the Node.js Remote Interpreter settings for your PhpStorm project.&lt;/p&gt;

&lt;p&gt;Select "Add" in the Node interpreter settings:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F9t0ntgv6rthdqld51loj.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.amazonaws.com%2Fuploads%2Farticles%2F9t0ntgv6rthdqld51loj.png" alt="Image description" width="800" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select "Docker" and use the same NodeJS version as you have  configured for your DDEV project, e.g. &lt;code&gt;node:22&lt;/code&gt;:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fp2fdwfybwqt0nzb7dkj4.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.amazonaws.com%2Fuploads%2Farticles%2Fp2fdwfybwqt0nzb7dkj4.png" alt="Image description" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F4dn42hk8tc4gah60obvp.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.amazonaws.com%2Fuploads%2Farticles%2F4dn42hk8tc4gah60obvp.png" alt="Image description" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setting applies only to the current project, so you can configure different NodeJS versions per project.&lt;/p&gt;

&lt;p&gt;That's it, happy developing!&lt;/p&gt;

&lt;p&gt;You can also disable the automatic path setting in the DDEV integration plugin for future projects:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fsx9xaelirhxy20515o4m.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.amazonaws.com%2Fuploads%2Farticles%2Fsx9xaelirhxy20515o4m.png" alt="Image description" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to checkout &lt;a href="https://ddev.com/s/discord" rel="noopener noreferrer"&gt;DDEV Discord&lt;/a&gt; as well, if you have more questions. 👋&lt;/p&gt;

</description>
      <category>ddev</category>
      <category>docker</category>
      <category>phpstorm</category>
      <category>node</category>
    </item>
    <item>
      <title>Vite is suddenly not working due to CORS error? 🧐 (DDEV)</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Thu, 06 Feb 2025 10:38:01 +0000</pubDate>
      <link>https://forem.com/mandrasch/vite-is-suddenly-not-working-anymore-due-to-cors-error-ddev-3673</link>
      <guid>https://forem.com/mandrasch/vite-is-suddenly-not-working-anymore-due-to-cors-error-ddev-3673</guid>
      <description>&lt;p&gt;If you work with a tool like DDEV and recently updated Vite, you will encounter the following error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Access to script at '&lt;a href="https://my-project.ddev.site:5173/@vite/client" rel="noopener noreferrer"&gt;https://my-project.ddev.site:5173/@vite/client&lt;/a&gt;' from origin '&lt;a href="https://my-project.ddev.site" rel="noopener noreferrer"&gt;https://my-project.ddev.site&lt;/a&gt;' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is because a security issue was reported to Vite: &lt;a href="https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6" rel="noopener noreferrer"&gt;Any websites were able to send any requests to the development server and read the response&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The issue was fixed in minor versions of Vite &amp;gt;=6.0.9, &amp;gt;=5.4.12 and &amp;gt;=4.5.6.&lt;/p&gt;

&lt;p&gt;In a nutshell: The issue was that attackers could send you a link to a malicious website and steal your JS source code by fetching it from your local Vite and submit it to their servers. &lt;/p&gt;

&lt;p&gt;Previous to the updated versions of Vite, &lt;a href="https://vite.dev/config/server-options#server-cors" rel="noopener noreferrer"&gt;server.cors&lt;/a&gt; was set to &lt;code&gt;true&lt;/code&gt; by default. That meant allowing all request origins to retrieve JS scripts or CSS from the Vite dev server (via header &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In the new versions of Vite, the default value of &lt;a href="https://vite.dev/config/server-options#server-cors" rel="noopener noreferrer"&gt;server.cors&lt;/a&gt; is now an object with a regular expression (regex) for origin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/(?:(?:[^&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.)?&lt;/span&gt;&lt;span class="sr"&gt;localhost|127&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;1|&lt;/span&gt;&lt;span class="se"&gt;\[&lt;/span&gt;&lt;span class="sr"&gt;::1&lt;/span&gt;&lt;span class="se"&gt;\])(?:&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new default regex ONLY allows requests from localhost, 127.0.0.1 and ::1. &lt;/p&gt;

&lt;p&gt;But when we're working with DDEV, the requests to the Vite dev server will be made from origins / local domains like &lt;code&gt;https://my-project.ddev.site&lt;/code&gt;. And this won't be allowed anymore by the new default setting. 🙅&lt;/p&gt;

&lt;h2&gt;
  
  
  Set it securely for DDEV
&lt;/h2&gt;

&lt;p&gt;Fortunately, fixing this is easy. In order to use a secure config for DDEV, use the following value for &lt;code&gt;server.cors.origin&lt;/code&gt; in  &lt;code&gt;vite.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;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="s2"&gt;vite&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="c1"&gt;// Your settings&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="c1"&gt;// Adjust Vites dev server to work with DDEV&lt;/span&gt;
  &lt;span class="c1"&gt;// https://vitejs.dev/config/server-options.html&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Respond to all network requests&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;strictPort&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="c1"&gt;// Defines the origin of the generated asset URLs during development, this must be set to the&lt;/span&gt;
    &lt;span class="c1"&gt;// Vite dev server URL and selected port. In general, `process.env.DDEV_PRIMARY_URL` will give&lt;/span&gt;
    &lt;span class="c1"&gt;// us the primary URL of the DDEV project, e.g. "https://test-vite.ddev.site". But since DDEV&lt;/span&gt;
    &lt;span class="c1"&gt;// can be configured to use another port (via `router_https_port`), the output can also be&lt;/span&gt;
    &lt;span class="c1"&gt;// "https://test-vite.ddev.site:1234". Therefore we need to strip a port number like ":1234"&lt;/span&gt;
    &lt;span class="c1"&gt;// before adding Vites port to achieve the desired output of "https://test-vite.ddev.site:5173".&lt;/span&gt;
    &lt;span class="na"&gt;origin&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="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;DDEV_PRIMARY_URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/:&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;:5173`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// 👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇&lt;/span&gt;
    &lt;span class="c1"&gt;// Configure CORS securely for the Vite dev server to allow requests from *.ddev.site domains,&lt;/span&gt;
    &lt;span class="c1"&gt;// supports additional hostnames (via regex). If you use another `project_tld`, adjust this.&lt;/span&gt;
    &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/([&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z0-9&lt;/span&gt;&lt;span class="se"&gt;\-\.]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?(\.&lt;/span&gt;&lt;span class="sr"&gt;ddev&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;site&lt;/span&gt;&lt;span class="se"&gt;)(?:&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&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;This regex allows requests from your local sites running on  &lt;code&gt;*.ddev.site&lt;/code&gt; domains. &lt;/p&gt;

&lt;p&gt;(It also supports projects running on a custom &lt;a href="https://ddev.readthedocs.io/en/stable/users/configuration/config/#router_https_port" rel="noopener noreferrer"&gt;router_https_port&lt;/a&gt; such as &lt;code&gt;https://my-project.ddev.site:1234&lt;/code&gt; via &lt;code&gt;(?::\d+)&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://ddev.com/blog/working-with-vite-in-ddev/" rel="noopener noreferrer"&gt;Working with Vite in DDEV - ddev.com&lt;/a&gt; for all details and further information. If you have questions, have a chat at &lt;a href="https://ddev.com/s/discord" rel="noopener noreferrer"&gt;DDEV Discord&lt;/a&gt;. Have fun with DDEV!&lt;/p&gt;

&lt;p&gt;Please note that this shouldn't be confused with &lt;a href="https://vite.dev/config/server-options#server-origin" rel="noopener noreferrer"&gt;&lt;code&gt;server.origin&lt;/code&gt;&lt;/a&gt; which needs to point to the Vite dev server URL (like &lt;code&gt;https://my-project.ddev.site:5173&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Other tools (Laravel Valet, Laravel Herd, etc.)
&lt;/h2&gt;

&lt;p&gt;For other local web dev tools, you can use a regex like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:/&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Za&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;-&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="nx"&gt;localhost&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)(?::&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use another domain ending (TLD) than &lt;code&gt;.test&lt;/code&gt;, adjust this to include your local domain of choice.&lt;/p&gt;

&lt;p&gt;Disclaimer: Not tested these settings myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security note ⚠️
&lt;/h2&gt;

&lt;p&gt;There is also advice out there that setting &lt;code&gt;cors: true&lt;/code&gt; will fix the error. Please be aware that this will open up the attack surface again and it totally defeats the purpose of the whole security fix. As stated in Vites official docs: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WARNING&lt;br&gt;
We recommend setting a specific value rather than true to avoid exposing the source code to untrusted origins.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(&lt;a href="https://vite.dev/config/server-options#server-cors" rel="noopener noreferrer"&gt;https://vite.dev/config/server-options#server-cors&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  PHP frameworks and CMSes
&lt;/h2&gt;

&lt;p&gt;I'll give some brief information here for some CMSes and frameworks, for the full list please check &lt;a href="https://ddev.com/blog/working-with-vite-in-ddev/" rel="noopener noreferrer"&gt;Working with Vite in DDEV - ddev.com&lt;/a&gt;. Please beware - this list here might be outdated soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Craft CMS
&lt;/h3&gt;

&lt;p&gt;The docs for &lt;a href="https://nystudio107.com/docs/vite/#using-ddev" rel="noopener noreferrer"&gt;DDEV usage of craft-vite&lt;/a&gt; have already been updated to get it working again.&lt;/p&gt;

&lt;p&gt;PRs for updating craft-vite: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nystudio107/craft-vite/pull/92" rel="noopener noreferrer"&gt;#92&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nystudio107/craft-vite/pull/99" rel="noopener noreferrer"&gt;#99&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Laravel Vite plugin
&lt;/h3&gt;

&lt;p&gt;Laravel maintains a &lt;a href="https://www.npmjs.com/package/laravel-vite-plugin" rel="noopener noreferrer"&gt;laravel-vite-plugin&lt;/a&gt; (NodeJS), it was updated to integrate the changes made in the Vite update:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/laravel/vite-plugin/releases/tag/v1.2.0" rel="noopener noreferrer"&gt;https://github.com/laravel/vite-plugin/releases/tag/v1.2.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(See this &lt;a href="https://github.com/laravel/vite-plugin/commit/b3a3abfebce73feec4af1e4bcb8cf0b6544b4574" rel="noopener noreferrer"&gt;commit&lt;/a&gt; for more information).&lt;/p&gt;

&lt;h3&gt;
  
  
  TYPO3
&lt;/h3&gt;

&lt;p&gt;The vite-plugin-typo3 made a hotfix, open issue: &lt;a href="https://github.com/s2b/vite-plugin-typo3/issues/16" rel="noopener noreferrer"&gt;#16&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Related links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Official DDEV guide: &lt;a href="https://ddev.com/blog/working-with-vite-in-ddev/" rel="noopener noreferrer"&gt;Working with Vite in DDEV - ddev.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6" rel="noopener noreferrer"&gt;Vite Security advisory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ddev/ddev.com/pull/313" rel="noopener noreferrer"&gt;PR for ddev.com article update&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PRs for updating craft-vite: &lt;a href="https://github.com/nystudio107/craft-vite/pull/92" rel="noopener noreferrer"&gt;#92&lt;/a&gt;, &lt;a href="https://github.com/nystudio107/craft-vite/pull/99" rel="noopener noreferrer"&gt;#99&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mandrasch/ddev-vite-simple-demo" rel="noopener noreferrer"&gt;DDEV Vite Simple DEMO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vitejs/vite/issues/19345" rel="noopener noreferrer"&gt;Show security warning for server.cors = true #19345&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;typo3-vite-plugin: &lt;a href="https://github.com/s2b/vite-plugin-typo3/issues/16" rel="noopener noreferrer"&gt;#16&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/laravel/vite-plugin/releases/tag/v1.2.0" rel="noopener noreferrer"&gt;laravel-vite-plugin Update&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Acknowledgements
&lt;/h2&gt;

&lt;p&gt;Thanks to &lt;a href="https://github.com/khalwat" rel="noopener noreferrer"&gt;Andrew Welch&lt;/a&gt; for providing the first regexes, &lt;a href="https://github.com/stasadev" rel="noopener noreferrer"&gt;Stanislav Zhuk&lt;/a&gt; for collaborating on the ddev.com PR, ViteJS core maintainer &lt;a href="https://github.com/sapphi-red" rel="noopener noreferrer"&gt;sapphi-red&lt;/a&gt; for improving the ddev.com PR and &lt;a href="https://github.com/s2b" rel="noopener noreferrer"&gt;Simon Praetorius&lt;/a&gt; for sending me infos  about how Laravel solved it. 🤝&lt;/p&gt;

</description>
      <category>vite</category>
      <category>docker</category>
      <category>ddev</category>
    </item>
    <item>
      <title>Optimize external / dynamic images on-the-fly in SvelteKit</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Sat, 01 Feb 2025 11:05:35 +0000</pubDate>
      <link>https://forem.com/mandrasch/optimize-external-dynamic-images-on-the-fly-in-sveltekit-25k8</link>
      <guid>https://forem.com/mandrasch/optimize-external-dynamic-images-on-the-fly-in-sveltekit-25k8</guid>
      <description>&lt;p&gt;If all your images are known at build time, &lt;a href="https://svelte.dev/docs/kit/images#sveltejs-enhanced-img" rel="noopener noreferrer"&gt;@sveltejs/enhanced-img&lt;/a&gt; is all you need for optimizing and delivering responsive images to your visitors. &lt;/p&gt;

&lt;p&gt;But what about external / dynamically loaded images?&lt;/p&gt;

&lt;h2&gt;
  
  
  Paid CDNs
&lt;/h2&gt;

&lt;p&gt;The easiest way to optimize external images is using a cloud service (CDN) for image optimization such as cloudinary, imgix, etc. Here is a &lt;a href="https://unpic.pics/providers/" rel="noopener noreferrer"&gt;list of providers&lt;/a&gt; which can be used via &lt;a href="https://unpic.pics/img/svelte/" rel="noopener noreferrer"&gt;@unpic/svelte&lt;/a&gt;. But these services cost money (if you exceed their free tiers). See &lt;a href="https://svelte.dev/docs/kit/images#Loading-images-dynamically-from-a-CDN" rel="noopener noreferrer"&gt;SvelteKit docs&lt;/a&gt; as well for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Selfhost an image optimizer
&lt;/h2&gt;

&lt;p&gt;Fortunately for us, there are selfhosting alternatives. 🥳&lt;/p&gt;

&lt;p&gt;Btw: Frameworks like NuxtJS already offer packages like NuxtImage where you can easily add ipx as selfhosted image optimizer to your project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://image.nuxt.com/providers/ipx" rel="noopener noreferrer"&gt;Nuxt/Image&lt;/a&gt; comes with a preconfigured instance of &lt;a href="https://github.com/unjs/ipx" rel="noopener noreferrer"&gt;unjs/ipx&lt;/a&gt;. An open source, self-hosted image optimizer based on &lt;a href="https://github.com/lovell/sharp" rel="noopener noreferrer"&gt;lovell/sharp&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For SvelteKit, this is discussed here: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sveltejs/kit/issues/241" rel="noopener noreferrer"&gt;Image processing #241&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sveltejs/kit/discussions/10808" rel="noopener noreferrer"&gt;SvelteKit needs an image optimisation library #10808&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sveltejs/kit/discussions/2994" rel="noopener noreferrer"&gt;Svelte image processing like Next &amp;amp; Nuxt. #2994&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But as long as there is no officially supported plugin, you can integrate an image optimizer like ipx easily by yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selfhost ipx (within SvelteKit)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/ipx" rel="noopener noreferrer"&gt;ipx&lt;/a&gt; is an easy-to-use image optimizer powered by sharp and svgo, written in NodeJS. It can be installed as package to your SvelteKit 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 i ipx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterwards, we need to integrate ipx into a &lt;a href="https://svelte.dev/docs/kit/adapter-node#Custom-server" rel="noopener noreferrer"&gt;custom express server&lt;/a&gt; route in SvelteKit. The whole approach requires using &lt;a href="https://svelte.dev/docs/kit/adapter-node#Usage" rel="noopener noreferrer"&gt;adapter-node&lt;/a&gt; in our project. &lt;/p&gt;

&lt;p&gt;Here is a demo repository with SvelteKit v2 and unpic v1: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mandrasch/sveltekit-ipx-unpic-demo" rel="noopener noreferrer"&gt;https://github.com/mandrasch/sveltekit-ipx-unpic-demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We switch to &lt;a href="https://svelte.dev/docs/kit/adapter-node" rel="noopener noreferrer"&gt;adapter-node&lt;/a&gt; and add the express package to our 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 i express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then add the express server configuration in a file like &lt;code&gt;my-server.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// my-server.js&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;handler&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;./build/handler.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&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;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;createIPX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;ipxHttpStorage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;createIPXNodeServer&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="s2"&gt;ipx&lt;/span&gt;&lt;span class="dl"&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// add a route that lives separately from the SvelteKit app&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/healthcheck&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ok&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="c1"&gt;// ipx image optimizer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ipx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createIPX&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Allowed domains for optimization&lt;/span&gt;
    &lt;span class="c1"&gt;// With httpStorage: ipxHttpStorage({ domains: ["your-domain.com", "second-domain.com"] }), ipx will optimize images coming for a given domain.&lt;/span&gt;
    &lt;span class="na"&gt;httpStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ipxHttpStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;domains&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="s2"&gt;picsum.photos&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/_ipx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;createIPXNodeServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ipx&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// let SvelteKit handle everything else, including serving prerendered pages and static assets&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listening on port 3000&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;In a nutshell, we implemented three server routes: One /healthcheck route, one &lt;code&gt;/_ipx&lt;/code&gt;-route which handles image optimization requests and serves optimized images via ipx's server and one serves your SvelteKit app as usual.&lt;/p&gt;

&lt;p&gt;We also configured the allowed domains for ipx - you can add your external domains from where you want to load images.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;package.json&lt;/code&gt; we add the following to the scripts section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node ./my-server.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the command which will be used when deploying and starting your app on production.&lt;/p&gt;

&lt;p&gt;The last step is to add &lt;a href="https://unpic.pics/img/svelte/" rel="noopener noreferrer"&gt;@unpic/svelte&lt;/a&gt; to our project, which supports ipx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @unpic/svelte
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use an external image from &lt;a href="https://picsum.photos/" rel="noopener noreferrer"&gt;https://picsum.photos/&lt;/a&gt; and retrieve the large 4K version (3840 x 2160 pixel) of it. We import the ipx transformer and tell unpic that ipx should handle the image optimization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// Important: Use base component which has transformer support&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;Image&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;@unpic/svelte/base&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
  &lt;span class="c1"&gt;// Import the transformer for the provider you are using&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;transform&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="s2"&gt;unpic/providers/ipx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;Image&lt;/span&gt;
    &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://picsum.photos/id/29/3840/2160"&lt;/span&gt;
    &lt;span class="na"&gt;layout=&lt;/span&gt;&lt;span class="s"&gt;"constrained"&lt;/span&gt;
    &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"winter mountain landscape"&lt;/span&gt;
    &lt;span class="na"&gt;transformer=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run this app via &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run start&lt;/code&gt;, the responsive HTML output of this will be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"winter mountain landscape"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"object-fit: cover; max-width: 800px; max-height: 600px; aspect-ratio: 1.33333 / 1; width: 100%;"&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="na"&gt;decoding=&lt;/span&gt;&lt;span class="s"&gt;"async"&lt;/span&gt; &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(min-width: 800px) 800px, 100vw"&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/_ipx/s_640x480/https://picsum.photos/id/29/3840/2160 640w,
/_ipx/s_750x563/https://picsum.photos/id/29/3840/2160 750w,
/_ipx/s_800x600/https://picsum.photos/id/29/3840/2160 800w,
/_ipx/s_828x621/https://picsum.photos/id/29/3840/2160 828w,
/_ipx/s_960x720/https://picsum.photos/id/29/3840/2160 960w,
/_ipx/s_1080x810/https://picsum.photos/id/29/3840/2160 1080w,
/_ipx/s_1280x960/https://picsum.photos/id/29/3840/2160 1280w,
/_ipx/s_1600x1200/https://picsum.photos/id/29/3840/2160 1600w"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/_ipx/s_800x600/https://picsum.photos/id/29/3840/2160"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, optimized image variants are served by the custom &lt;code&gt;/_ipx&lt;/code&gt;-route 🎉&lt;/p&gt;

&lt;p&gt;Learn more about all unpic options: &lt;a href="https://unpic.pics/learn/" rel="noopener noreferrer"&gt;https://unpic.pics/learn/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use the regular &lt;code&gt;npm run dev&lt;/code&gt;, you won't see the optimized images since the Vite devserver doesn't know about ipx. If you wan't, you can &lt;a href="https://zenn.dev/liquitous/articles/c2acb2764b6c40#ipx%E3%82%92vite%E3%81%AE%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%A8%E3%81%97%E3%81%A6%E3%82%82%E8%A8%AD%E5%AE%9A" rel="noopener noreferrer"&gt;use a Vite plugin as described in the tutorial by Kazuumi Nishimura&lt;/a&gt;. Beware: The tutorial was written for an earlier version of unpic, not the latest unpic v1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selfhost imgproxy
&lt;/h3&gt;

&lt;p&gt;Another alternative for selfhosting is &lt;a href="https://github.com/imgproxy/imgproxy" rel="noopener noreferrer"&gt;imgproxy&lt;/a&gt;. This works more like an external CDN.&lt;/p&gt;

&lt;p&gt;You could selfhost it for instance with &lt;a href="https://coolify.io/" rel="noopener noreferrer"&gt;Coolify&lt;/a&gt;, the one click service "Next image transformation" ships the docker image &lt;a href="https://hub.docker.com/r/darthsim/imgproxy/" rel="noopener noreferrer"&gt;darthsim/imgproxy&lt;/a&gt;:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fcyt90kup6al5j0nkp64o.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.amazonaws.com%2Fuploads%2Farticles%2Fcyt90kup6al5j0nkp64o.png" alt="Coolify add new service screenshot" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beware: The official imgproxy Docker repository was moved to &lt;a href="https://github.com/imgproxy/imgproxy/pkgs/container/imgproxy" rel="noopener noreferrer"&gt;ghcr.io/imgproxy/imgproxy⁠&lt;/a&gt;. The repository on Docker Hub will be kept available, but we may stop updating images in it eventually. So you might want to install it as Docker image from there via Coolify (or other services)&lt;/p&gt;

&lt;p&gt;I didn't find specific tutorials for SvelteKit for the usage of imgproxy, but the usage should be most likely similiar to these tutorials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://mxd.codes/articles/optimizing-images-for-next-js-sites-with-imgproxy-and-docker" rel="noopener noreferrer"&gt;Optimizing images for Next.js sites with imgproxy and docker
&lt;/a&gt; by Max Dietrich&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I assume you would need to write a custom image loading function, since imgproxy seems not yet be supported by &lt;a href="https://unpic.pics/img/svelte/" rel="noopener noreferrer"&gt;@unpic/svelte&lt;/a&gt;. But there is an ongoing discussion: &lt;a href="https://github.com/ascorbic/unpic/issues/37" rel="noopener noreferrer"&gt;Feature Request: Support for Self Hosted image manipulation server (Thumbor, Imgproxy, Imaginary) #37&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're interested in selfhosting and Coolify, make sure to check out &lt;a href="https://dev.to/mandrasch/deploy-sveltekit-with-ssr-on-coolify-hetzner-vps-24c5"&gt;Deploy SvelteKit with SSR on Coolify (Hetzner VPS)&lt;/a&gt; as well.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other alternatives?
&lt;/h2&gt;

&lt;p&gt;In the GitHub discussion about image processing in SvelteKit, developer mrxbox98 posted about &lt;a href="https://github.com/sveltejs/kit/issues/241#issuecomment-2221807232" rel="noopener noreferrer"&gt;his experiment of using sharp directly in SvelteKit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are also other alternatives for selfhosting such as &lt;a href="https://www.thumbor.org/" rel="noopener noreferrer"&gt;https://www.thumbor.org/&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Let's hope that this will be a bit easier in future with an image library similiar to NuxtImage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you know about other tutorials or alternatives, please let me know in the comments - thanks! 🙏&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Acknowledgements: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This article section about ipx is based on the wonderful (japanese) tutorial by &lt;a href="https://dev.to/kazuumin"&gt;Kazuumi Nishimura&lt;/a&gt; showing all the necessary steps: &lt;a href="https://zenn.dev/liquitous/articles/c2acb2764b6c40" rel="noopener noreferrer"&gt;SvelteKit + ipx + unpic dynamic and optimal image delivery&lt;/a&gt;. Beware: It was written for an earlier version of unpic, not the latest unpic v1.&lt;/li&gt;
&lt;li&gt;Thanks very much to &lt;a href="https://github.com/tobimori" rel="noopener noreferrer"&gt;tobimori&lt;/a&gt; for giving some helpful hints about image processing within JS frameworks (ipx/sharp) and thanks to Jens for the idea to write an article about this and reviewing it.&lt;/li&gt;
&lt;li&gt;Thanks to &lt;a href="https://social.tchncs.de/@grooovinger@mastodon.social/113922506130413089" rel="noopener noreferrer"&gt;Martin Grubinger&lt;/a&gt; for the info about imgproxy via Coolify.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>svelte</category>
      <category>sveltekit</category>
      <category>ipx</category>
      <category>unpic</category>
    </item>
    <item>
      <title>Svelte 5: Share state between components (for Dummies)</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Thu, 02 Jan 2025 12:26:10 +0000</pubDate>
      <link>https://forem.com/mandrasch/svelte-5-share-state-between-components-for-dummies-4gd2</link>
      <guid>https://forem.com/mandrasch/svelte-5-share-state-between-components-for-dummies-4gd2</guid>
      <description>&lt;p&gt;As soon as you see the new &lt;a href="https://svelte.dev/docs/svelte/$state" rel="noopener noreferrer"&gt;&lt;code&gt;$state&lt;/code&gt;&lt;/a&gt; in Svelte 5, you might be tempted to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// sharedState.svelte.js&lt;/span&gt;

 &lt;span class="c1"&gt;// This won't work as export! ❌&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;searchState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&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 svelte"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- App.svelte --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;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;searchState&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;./sharedState.svelte.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
    &lt;span class="c1"&gt;// This won't work! ❌&lt;/span&gt;
    &lt;span class="nx"&gt;searchState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bicycles&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/scrip&lt;/span&gt;&lt;span class="err"&gt;t
&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;onclick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Search&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;bicycles&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above doesn't work if you export it - and here is why: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;„You're encountering the most complicated part of Svelte 5. How reactivity works and how the compiler hides it from you.&lt;/p&gt;

&lt;p&gt;When you export a single value like a number or string, there is no mechanism for Svelte to maintain reactivity because JavaScript doesn't offer a way to track that.“&lt;br&gt;
Mat Simon&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Huge thanks to &lt;a href="https://bsky.app/profile/matsimon.dev/" rel="noopener noreferrer"&gt;Mat Simon&lt;/a&gt; who explained this to me in a &lt;a href="https://bsky.app/profile/matsimon.dev/post/3leqnvoohv22w" rel="noopener noreferrer"&gt;Bluesky thread&lt;/a&gt; 💡 &lt;/p&gt;

&lt;p&gt;Here is what I learned so far:&lt;/p&gt;

&lt;h2&gt;
  
  
  For exports, use $state with Objects - not Strings!
&lt;/h2&gt;

&lt;p&gt;As said, we can't use a String (or a Number) directly for exporting states like we usually do in a single file component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// sharedState.svelte.js&lt;/span&gt;
&lt;span class="c1"&gt;// This won't work as export! ❌&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;searchState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;(&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;countState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, Objects in $state() get all their property values proxied automatically by Svelte v5. Wrapping your String value into an Object allows you to export that state and share it between files and components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// sharedState.svelte.js&lt;/span&gt;

&lt;span class="c1"&gt;// Instead of this ...&lt;/span&gt;
&lt;span class="c1"&gt;// export const searchState = $state("");&lt;/span&gt;
&lt;span class="c1"&gt;// ... do this:&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;searchState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Instead of this ...&lt;/span&gt;
&lt;span class="c1"&gt;// export const countState = $state(0);&lt;/span&gt;
&lt;span class="c1"&gt;// ... do this:&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;countState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that &lt;code&gt;searchState&lt;/code&gt; and &lt;code&gt;countState&lt;/code&gt; are turned into an Svelte state proxy with a getter and a setter for the &lt;code&gt;.text&lt;/code&gt; property. &lt;/p&gt;

&lt;p&gt;When you import a $state Object, and &lt;em&gt;then&lt;/em&gt; update the property via &lt;code&gt;.text = "newValue"&lt;/code&gt;, you're using the Svelte setter to update the state. This will then be updated in all other places where the state is used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;// App.svelte

import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchState&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from './sharedState.svelte.js'

function handleClick()&lt;span class="si"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// uses the automatically created setter&lt;/span&gt;
    &lt;span class="nx"&gt;searchState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bicycles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Search for bicycles&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Demo (REPL): &lt;a href="https://svelte.dev/playground/bce842d6da9b442bbc227355df7c2e4c?version=5.16.0" rel="noopener noreferrer"&gt;Very basic $state example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can choose any property name you want, as well as multiple properties per $state Object. Svelte 5 takes care of it automagically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// number&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;countState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// multiple properties (number, string)&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;anotherState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// array&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;tagsState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;selectedTags&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;Full technical background explained &lt;a href="https://bsky.app/profile/matsimon.dev/post/3letmmmg6ys2a" rel="noopener noreferrer"&gt;by Mat Simon&lt;/a&gt;: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Svelte doesn't have to proxy ever method that modifies the underlying object. That would be pretty error prone. Every time JavaScript adds a new method, the Svelte team would need to adapt immediately, otherwise reactivity would break for that specific method. In reality &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="noopener noreferrer"&gt;JavaScript proxies&lt;/a&gt; are pretty cool. They offer get() and set() traps. [..] This means that the actual implementation of the array (and object) proxy is quite simple and handles all possible methods the array implements and might implement in the future. See &lt;a href="https://svelte.dev/docs/svelte/$state" rel="noopener noreferrer"&gt;Svelte docs&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://svelte.dev/docs/svelte/$state" rel="noopener noreferrer"&gt;Svelte docs&lt;/a&gt; state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If $state is used with an array or a simple object, the result is a deeply reactive state proxy. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The official tutorial for this is &lt;a href="https://svelte.dev/tutorial/svelte/universal-reactivity" rel="noopener noreferrer"&gt;Universal Reactivity&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beware: Don't re-assign Objects outside of their scope
&lt;/h3&gt;

&lt;p&gt;When you use an Object they are also not states themselves! That's important to understand. If you do the following after an import, you lost reactivity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;searchState&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;./sharedState.svelte.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// don't do this!&lt;/span&gt;
&lt;span class="nx"&gt;searchState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no way for Svelte to handle this for you. Always use the automatic Svelte getter/setter for exports/imports via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;searchState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: In the same scope, if Objects are defined with $state(), they can be overridden. This article is only about exports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced: Use classes
&lt;/h3&gt;

&lt;p&gt;There are multiple options to define your state objects, you can also use classes if you ever needed custom methods for updating values in your state: &lt;a href="https://joyofcode.xyz/how-to-share-state-in-svelte-5#using-classes-for-reactive-state" rel="noopener noreferrer"&gt;https://joyofcode.xyz/how-to-share-state-in-svelte-5#using-classes-for-reactive-state&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Share state between components
&lt;/h2&gt;

&lt;p&gt;So we know how to import (and update) states inside components and we know that we can use objects out of the box with &lt;code&gt;$state&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;// MyComponent.svelte

import &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;searchState&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; from './sharedState.svelte.js'

function handleClick()&lt;span class="si"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;searchState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bicycles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Search for bicycles&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can even pass down the $state object as reference by a property with &lt;a href="https://svelte.dev/docs/svelte/$props" rel="noopener noreferrer"&gt;$props&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;// App.svelte

&lt;span class="nt"&gt;&amp;lt;script&amp;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;searchTextState&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;./data.svelte.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ResultList&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;./ResultList.svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;ResultList&lt;/span&gt; &lt;span class="na"&gt;stateObj=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;searchTextState&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;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 svelte"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ResultList.svelte --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// reference to state object is passed down as prop of component&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stateObj&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$props&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You're searching for &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stateObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But how do you know that the state changed somewhere in your app when you're inside a component and want to do some processing based on that value? That's what &lt;a href="https://svelte.dev/docs/svelte/$derived" rel="noopener noreferrer"&gt;$derived&lt;/a&gt; and &lt;a href="https://svelte.dev/docs/svelte/$derived#$derived.by" rel="noopener noreferrer"&gt;$derived.by&lt;/a&gt; are for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ResultList.svelte --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// reference to state object is passed down as prop&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stateObj&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$props&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Listen for state changes&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;resultString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$derived&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;by&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;state change detected&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="nx"&gt;stateObj&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

           &lt;span class="c1"&gt;// we would filter results here, do custom stuff&lt;/span&gt;

           &lt;span class="c1"&gt;// for now, we just mess with the search text&lt;/span&gt;
           &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stateObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
           &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;uppercaseText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

           &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`You are searching for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;uppercaseText&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You're searching for &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resultString&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple Demo (REPL): &lt;a href="https://svelte.dev/playground/4d8fd2686e1048759ee451494b00817e?version=5.16.0" rel="noopener noreferrer"&gt;Share $state between components (simple)&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage with bind:value
&lt;/h3&gt;

&lt;p&gt;As you might already know, there is no need to write change handler functions for text inputs. You can just use &lt;a href="https://svelte.dev/tutorial/svelte/text-inputs" rel="noopener noreferrer"&gt;&lt;code&gt;bind:value&lt;/code&gt;&lt;/a&gt; to update the state automatically when text is entered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- App.svelte --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;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;searchTextState&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;./data.svelte.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SearchInput&lt;/span&gt; &lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;stateObjPropToChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;searchTextState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;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 svelte"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- SearchInput.svelte --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stateObjPropToChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$bindable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$props&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
   Search text:
   &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;bind:value=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stateObjPropToChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to use &lt;code&gt;bind:&lt;/code&gt; when passing the prop to the component and  use &lt;a href="https://svelte.dev/docs/svelte/$bindable" rel="noopener noreferrer"&gt;$bindable()&lt;/a&gt; when receiving the prop in the component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage with bind:group
&lt;/h3&gt;

&lt;p&gt;Multiple checkbox inputs can be handled with Svelte via &lt;a href="https://svelte.dev/tutorial/svelte/group-inputs" rel="noopener noreferrer"&gt;&lt;code&gt;bind-group=&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To use bind:group with shared state, make sure to use &lt;code&gt;bind:&lt;/code&gt; when passing the prop to the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// state.svelte.js &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;selectedColorsState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$state&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;selectedValues&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 svelte"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- App.svelte --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;FilterCheckboxes&lt;/span&gt; 
    &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"Colors"&lt;/span&gt; 
    &lt;span class="na"&gt;availableOptions=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;availableColorOptions&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
    &lt;span class="na"&gt;bind:statePropToBind=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;selectedColorsState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectedValues&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And make sure to use &lt;a href="https://svelte.dev/docs/svelte/$bindable" rel="noopener noreferrer"&gt;$bindable()&lt;/a&gt; when receiving the prop in the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- FilterCheckboxes.svelte --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="nx"&gt;availableOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="c1"&gt;// important: you need to receive a bindable here, &lt;/span&gt;
        &lt;span class="c1"&gt;// to update states back to parent components&lt;/span&gt;
        &lt;span class="nx"&gt;statePropToBind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$bindable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$props&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Simple Demo (REPL): &lt;a href="https://svelte.dev/playground/17af110213b9434d95dff6ae2e62c100?version=5.16.0" rel="noopener noreferrer"&gt;Search and filter with checkbox group components and v5 $state &amp;amp; $derived&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But beware, the [Svelte docs for bind:group] state: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;bind:group only works if the inputs are in the same Svelte component.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you have two components with different values for selection, &lt;code&gt;bind-group&lt;/code&gt; won't work. You should split this into separate states. See &lt;a href="https://github.com/sveltejs/svelte/issues/2308" rel="noopener noreferrer"&gt;bind:group does not work with nested components #2308&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;Disclaimer: If you want to use filter checkboxes with URL params (query parameters) in SvelteKit, it might be easier to derive the global state from &lt;code&gt;page.url.searchParams&lt;/code&gt; via &lt;code&gt;import {page} from&lt;/code&gt;$app/state&lt;code&gt;and&lt;/code&gt;$derived`. See &lt;a href="https://github.com/mandrasch/svelte-nobel-prize-demo" rel="noopener noreferrer"&gt;https://github.com/mandrasch/svelte-nobel-prize-demo&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;There is a big difference between using &lt;code&gt;$state()&lt;/code&gt; inside one file (one scope) - or using &lt;code&gt;$state()&lt;/code&gt; as export / import.&lt;/p&gt;

&lt;p&gt;Happy to get your (critical) feedback on this article! 🙏 &lt;/p&gt;

&lt;h2&gt;
  
  
  More resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bluesky discussion: &lt;a href="https://bsky.app/profile/mandrasch.bsky.social/post/3leopifb4zc2z" rel="noopener noreferrer"&gt;#Svelte v5 - is this the easiest way to use $state (and $derived) with multiple checkboxes to filter data dynamically? 🤔&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://joyofcode.xyz/how-to-share-state-in-svelte-5" rel="noopener noreferrer"&gt;Different Ways To Share State In Svelte 5 - Joy of Code&lt;/a&gt; + &lt;a href="https://www.youtube.com/watch?v=qI31XOrBuY0" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=5urk4ui_l5o" rel="noopener noreferrer"&gt;Lets Build A Filtering System with Svelte 5 , Sveltekit 2, Tailwind, Upstash (2024) - Lawal Adebola&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nKJbOf4mqeE" rel="noopener noreferrer"&gt;Svelte 5 - Global $state (convert stores to $state runes) - Svelte Mastery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=-SM77ksjpJI" rel="noopener noreferrer"&gt;Svelte 5 Runes Demystified (1/4) - Signal Reactivity Basics - Peter Makes Websites Ltd&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Nerd discussion: &lt;a href="https://www.reddit.com/r/sveltejs/comments/1htup7k/ive_been_championing_svelte_for_3_years_and_runes/" rel="noopener noreferrer"&gt;I’ve been championing Svelte for 3+ years, and runes are killing me.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official tutorial: &lt;a href="https://svelte.dev/tutorial/svelte/universal-reactivity" rel="noopener noreferrer"&gt;Universal reactivity&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Bonus: &lt;a href="https://www.youtube.com/watch?v=HRz_rU2BlZc" rel="noopener noreferrer"&gt;Understanding Effects In Svelte 5 And When To Use Them&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demos:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;New: &lt;a href="https://github.com/SikandarJODD/youva" rel="noopener noreferrer"&gt;https://github.com/SikandarJODD/youva&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://svelte.dev/playground/bce842d6da9b442bbc227355df7c2e4c?version=5.16.0" rel="noopener noreferrer"&gt;Very basic $state example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://svelte.dev/playground/4d8fd2686e1048759ee451494b00817e?version=5.16.0" rel="noopener noreferrer"&gt;Share $state between components (simple)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://svelte.dev/playground/17af110213b9434d95dff6ae2e62c100?version=5.16.0" rel="noopener noreferrer"&gt;Search and filter with checkbox group components and v5 $state &amp;amp; $derived&lt;/a&gt; (WIP)&lt;/li&gt;
&lt;li&gt;Full SvelteKit example: &lt;a href="https://github.com/mandrasch/austrian-web-dev-companies" rel="noopener noreferrer"&gt;https://github.com/mandrasch/austrian-web-dev-companies&lt;/a&gt; (WIP)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced: Use SvelteSet, SvelteMap, SvelteDate, etc.
&lt;/h2&gt;

&lt;p&gt;Okay, objects are fine and handled by Svelte automagically - we got it. &lt;/p&gt;

&lt;p&gt;But what about &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date" rel="noopener noreferrer"&gt;&lt;code&gt;Date&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/URL" rel="noopener noreferrer"&gt;&lt;code&gt;URL&lt;/code&gt;&lt;/a&gt; and more built-in objects of standard JavaScript? And if you're more experienced in JavaScript, you might know that there are some more advanced data types (standard built-in objects):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Set" rel="noopener noreferrer"&gt;&lt;code&gt;Set&lt;/code&gt;&lt;/a&gt; object lets you store unique values of any type, whether primitive values or object references.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map" rel="noopener noreferrer"&gt;&lt;code&gt;Map&lt;/code&gt;&lt;/a&gt; object holds key-value pairs and remembers the original insertion order of the keys. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to use these with reactive $state, you need to use their corresponding Svelte wrapper from &lt;a href="https://svelte.dev/docs/svelte/svelte-reactivity" rel="noopener noreferrer"&gt;&lt;code&gt;svelte/reactivity&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MediaQuery&lt;/li&gt;
&lt;li&gt;SvelteDate&lt;/li&gt;
&lt;li&gt;SvelteMap&lt;/li&gt;
&lt;li&gt;SvelteSet&lt;/li&gt;
&lt;li&gt;SvelteURL&lt;/li&gt;
&lt;li&gt;SvelteURLSearchParams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason there is a separate &lt;code&gt;SvelteSet&lt;/code&gt; and &lt;code&gt;SvelteMap&lt;/code&gt; class (instead of just rewriting it automatically like they do with objects and arrays) is because they wanted to draw a line somewhere since they can't proxy every conceivable object. See &lt;a href="https://github.com/sveltejs/svelte/issues/10263" rel="noopener noreferrer"&gt;Reactive Map, Set, Date and URL #10263&lt;/a&gt; for technical details.&lt;/p&gt;

&lt;p&gt;How can you use it? Simple as that:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;svelte&lt;br&gt;
// sharedState.svelte.js&lt;br&gt;
import { SvelteSet } from 'svelte/reactivity'&lt;br&gt;
export const selectedColors = new SvelteSet(['red'])&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But beware: If you want to output a &lt;code&gt;SvelteSet&lt;/code&gt;, make sure to use this (or use the new [$inspect])(&lt;a href="https://svelte.dev/docs/svelte/$inspect):" rel="noopener noreferrer"&gt;https://svelte.dev/docs/svelte/$inspect):&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
{JSON.stringify({selectedColors: [... selectedColors]})}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgements
&lt;/h2&gt;

&lt;p&gt;Thanks very much to &lt;a href="https://bsky.app/profile/matsimon.dev" rel="noopener noreferrer"&gt;Mat Simon&lt;/a&gt; and &lt;a href="https://discord.com/channels/457912077277855764/1325055109318709278" rel="noopener noreferrer"&gt;hfcRed (Svelte Discord)&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>state</category>
    </item>
    <item>
      <title>CraftCMS Launchpad - interactive CraftCMS demos in your browser</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Thu, 12 Sep 2024 06:33:32 +0000</pubDate>
      <link>https://forem.com/mandrasch/craftcms-launchpad-interactive-craftcms-demos-in-your-browser-4c1f</link>
      <guid>https://forem.com/mandrasch/craftcms-launchpad-interactive-craftcms-demos-in-your-browser-4c1f</guid>
      <description>&lt;p&gt;I had some time on the weekend and did the first prototype of: CraftCMS Launchpad - interactive CraftCMS demos in your browser, powered by DDEV (Docker).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://craftcms-launchpad.mandrasch.eu/" rel="noopener noreferrer"&gt;https://craftcms-launchpad.mandrasch.eu/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The project is inspired by &lt;a href="https://astro.new/latest/" rel="noopener noreferrer"&gt;https://astro.new/latest/&lt;/a&gt; - in JS-land it's totally common to have a lot of demos launchable in your browser, a thing I missed a bit in the Craft Community.&lt;/p&gt;

&lt;p&gt;Happy to receive feedback &amp;amp; if you have cool demos or starters, please let me know! &lt;/p&gt;

</description>
      <category>craftcms</category>
      <category>php</category>
      <category>gitpod</category>
      <category>docker</category>
    </item>
    <item>
      <title>Install Laravel with Vite support in DDEV (Docker)</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Sun, 19 May 2024 09:18:57 +0000</pubDate>
      <link>https://forem.com/mandrasch/install-laravel-with-vite-support-in-ddev-docker-4lmh</link>
      <guid>https://forem.com/mandrasch/install-laravel-with-vite-support-in-ddev-docker-4lmh</guid>
      <description>&lt;p&gt;You want to start developing with Laravel in DDEV and Docker -  including Vite support? It's easy!&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Setup a new DDEV project
&lt;/h2&gt;

&lt;p&gt;Create your new folder and switch to it:&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;mkdir &lt;/span&gt;my-laravel-site &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-laravel-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to create a new DDEV project configuration, the most simple starting point would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ddev config --project-type=laravel --docroot=public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This commands creates the &lt;code&gt;.ddev/config.yaml&lt;/code&gt; configuration file (which can be shared with team members in git):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-laravel-site&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;laravel&lt;/span&gt;
&lt;span class="na"&gt;docroot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
&lt;span class="na"&gt;php_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8.2"&lt;/span&gt;
&lt;span class="na"&gt;webserver_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-fpm&lt;/span&gt;
&lt;span class="na"&gt;xdebug_enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;additional_hostnames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;additional_fqdns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10.11"&lt;/span&gt;
&lt;span class="na"&gt;use_dns_when_possible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;composer_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
&lt;span class="na"&gt;web_environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;corepack_enable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also choose other database types and version, as well as your PHP and NodeJS version of choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# MySQL v8.0, PHP 8.2, NodeJS 20
ddev config --project-type=laravel --docroot=public --php-version="8.2" --database="mysql:8.0" --nodejs-version="20"

# PostgreSQL v16, PHP 8.2, NodeJS 20
ddev config --project-type=laravel --docroot=public --php-version="8.2" --database="postgres:16" --nodejs-version="20"

# MariaDB 10.11, PHP 8.2, NodeJS 20
ddev config --project-type=laravel --docroot=public --php-version="8.2" --database="mariadb:10.11" --nodejs-version="20"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can either do this via CLI command - or just edit the &lt;code&gt;.ddev/config.yaml&lt;/code&gt; file itself. See &lt;a href="https://ddev.readthedocs.io/en/stable/users/configuration/config/" rel="noopener noreferrer"&gt;config.yaml options&lt;/a&gt; for more information. &lt;/p&gt;

&lt;p&gt;For NodeJS, you can also use use a specific version to avoid "it works on my machine" problems or edge cases: &lt;code&gt;--nodejs-version="20.12.1"&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Btw: There is also support for the new SQLite defaults of laravel, see &lt;a href="https://ddev.readthedocs.io/en/stable/users/quickstart/#laravel" rel="noopener noreferrer"&gt;DDEV docs&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;ddev start&lt;/code&gt; to launch your new project 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Install Laravel
&lt;/h2&gt;

&lt;p&gt;Run this command to install the latest stable laravel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ddev composer create laravel/laravel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to install a specific version, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ddev composer create &lt;span class="s2"&gt;"laravel/laravel:^11"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database connection settings in &lt;code&gt;.env&lt;/code&gt; will be automatically inserted by ddev. Btw: You can view your database with viewers like &lt;code&gt;ddev sequelace&lt;/code&gt;, &lt;code&gt;ddev dbeaver&lt;/code&gt;, &lt;code&gt;ddev tableplus&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;ddev launch&lt;/code&gt; to open the welcome page in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Add Vite support
&lt;/h2&gt;

&lt;p&gt;The newer laravel versions already have a Vite integration, there are just a two tweaks needed for usage in DDEV:&lt;/p&gt;

&lt;p&gt;First, we need to expose the Vite port 5173 in our Docker container. Add this to the &lt;code&gt;.ddev/config.yaml&lt;/code&gt;-file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;web_extra_exposed_ports:
  - name: node-vite
    container_port: 5173
    http_port: 5172
    https_port: 5173
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;ddev restart&lt;/code&gt; is needed afterwards. ⚠️ &lt;/p&gt;

&lt;p&gt;Secondly, we need to adjust the &lt;code&gt;vite.config.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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;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;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;laravel&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;laravel-vite-plugin&lt;/span&gt;&lt;span class="dl"&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;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5173&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;origin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&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;DDEV_PRIMARY_URL&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="nx"&gt;port&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="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;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;laravel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;input&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;resources/css/app.css&lt;/span&gt;&lt;span class="dl"&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;resources/js/app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;refresh&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="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;// Adjust Vites dev server to work with DDEV&lt;/span&gt;
  &lt;span class="c1"&gt;// https://vitejs.dev/config/server-options.html&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Respond to all network requests&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5173&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;strictPort&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="c1"&gt;// Defines the origin of the generated asset URLs during development, this must be set to the&lt;/span&gt;
    &lt;span class="c1"&gt;// Vite dev server URL and selected port. In general, `process.env.DDEV_PRIMARY_URL` will give&lt;/span&gt;
    &lt;span class="c1"&gt;// us the primary URL of the DDEV project, e.g. "https://test-vite.ddev.site". But since DDEV&lt;/span&gt;
    &lt;span class="c1"&gt;// can be configured to use another port (via `router_https_port`), the output can also be&lt;/span&gt;
    &lt;span class="c1"&gt;// "https://test-vite.ddev.site:1234". Therefore we need to strip a port number like ":1234"&lt;/span&gt;
    &lt;span class="c1"&gt;// before adding Vites port to achieve the desired output of "https://test-vite.ddev.site:5173".&lt;/span&gt;
    &lt;span class="na"&gt;origin&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="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;DDEV_PRIMARY_URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/:&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;:5173`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// 👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇&lt;/span&gt;
    &lt;span class="c1"&gt;// Configure CORS securely for the Vite dev server to allow requests from *.ddev.site domains,&lt;/span&gt;
    &lt;span class="c1"&gt;// supports additional hostnames (via regex). If you use another `project_tld`, adjust this.&lt;/span&gt;
    &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/([&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z0-9&lt;/span&gt;&lt;span class="se"&gt;\-\.]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?(\.&lt;/span&gt;&lt;span class="sr"&gt;ddev&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;site&lt;/span&gt;&lt;span class="se"&gt;)(?:&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&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;Great, you can now run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ddev npm install
ddev npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By running &lt;code&gt;ddev npm run dev&lt;/code&gt;, laravel will automatically generate a &lt;code&gt;public/hot&lt;/code&gt; file which points to the Vite devserver and port.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Develop with Vite
&lt;/h2&gt;

&lt;p&gt;The default welcome template of Laravel has no Vite integration yet. &lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;resources/views/welcome.blade.php&lt;/code&gt; with something like this:&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;!DOCTYPE html&amp;gt;
&amp;lt;html lang="{{ str_replace('_', '-', app()-&amp;gt;getLocale()) }}"&amp;gt;

&amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1"&amp;gt;
    &amp;lt;!-- Include JS and CSS via vite, see https://laravel.com/docs/11.x/vite --&amp;gt;
    @vite('resources/js/app.js')
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;

    &amp;lt;h1&amp;gt;Hello, vite!&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also import the app.css file in &lt;code&gt;resources/js/app.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bootstrap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../css/app.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally - add some styles to &lt;code&gt;resources/css/app.css&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;blueviolet&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;If you open &lt;a href="https://my-laravel-site.ddev.site/" rel="noopener noreferrer"&gt;https://my-laravel-site.ddev.site/&lt;/a&gt;, you should immediately see style changes provided by Vite when you edit your css or js files.&lt;/p&gt;

&lt;p&gt;You can check out a full demo repository here: &lt;a href="https://github.com/mandrasch/ddev-laravel-vite" rel="noopener noreferrer"&gt;https://github.com/mandrasch/ddev-laravel-vite&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please let me know if there could be any improvements to this guide! Have fun developing!&lt;/p&gt;

&lt;h2&gt;
  
  
  The golden rule of "ddev ..."
&lt;/h2&gt;

&lt;p&gt;When working with DDEV, please make always sure to use &lt;code&gt;ddev ...&lt;/code&gt; in front of commands such as &lt;code&gt;ddev composer&lt;/code&gt;, &lt;code&gt;ddev npm&lt;/code&gt; (or &lt;code&gt;ddev yarn&lt;/code&gt;), etc. &lt;/p&gt;

&lt;p&gt;Otherwise your commands will be executed with your local computers PHP or NodeJS, not in the Docker container (which is the whole point of DDEV). You can also jump into the web container with &lt;code&gt;ddev ssh&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  More resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;DDEV Discord: &lt;a href="https://discord.gg/5wjP76mBJD" rel="noopener noreferrer"&gt;https://discord.gg/5wjP76mBJD&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://github.com/mandrasch/ddev-laravel-vite" rel="noopener noreferrer"&gt;https://github.com/mandrasch/ddev-laravel-vite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://github.com/mandrasch/ddev-laravel-breeze-vite" rel="noopener noreferrer"&gt;https://github.com/mandrasch/ddev-laravel-breeze-vite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://github.com/mandrasch/ddev-laravel-breeze-livewire" rel="noopener noreferrer"&gt;https://github.com/mandrasch/ddev-laravel-breeze-livewire&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ddev.readthedocs.io/en/stable/users/configuration/config/" rel="noopener noreferrer"&gt;config.yaml options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vite + DDEV Guide: &lt;a href="https://ddev.com/blog/working-with-vite-in-ddev/" rel="noopener noreferrer"&gt;https://ddev.com/blog/working-with-vite-in-ddev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DDEV quickstart docs: &lt;a href="https://ddev.readthedocs.io/en/stable/users/quickstart/#laravel" rel="noopener noreferrer"&gt;https://ddev.readthedocs.io/en/stable/users/quickstart/#laravel&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>laravel</category>
      <category>php</category>
      <category>vite</category>
    </item>
    <item>
      <title>Deploy SvelteKit with SSR on Coolify (Hetzner VPS)</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Sat, 27 Apr 2024 12:25:08 +0000</pubDate>
      <link>https://forem.com/mandrasch/deploy-sveltekit-with-ssr-on-coolify-hetzner-vps-24c5</link>
      <guid>https://forem.com/mandrasch/deploy-sveltekit-with-ssr-on-coolify-hetzner-vps-24c5</guid>
      <description>&lt;p&gt;This is my first quick try deploying SvelteKit with the open source software &lt;a href="https://coolify.io/" rel="noopener noreferrer"&gt;Coolify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Coolify is an "open-source &amp;amp; self-hostable Heroku / Netlify / Vercel alternative". It is developed by &lt;a href="https://twitter.com/heyandras" rel="noopener noreferrer"&gt;Andras Bacsai&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The advantage of Coolify compared to other awesome server management tools like ploi.io (&lt;a href="https://dev.to/mandrasch/host-sveltekit-apps-with-ssr-support-via-ploiio-on-hetzner-cloud-1cpa"&gt;SvelteKit ploi.io tutorial&lt;/a&gt;), Cleavr or Laravel Forge is that you can install it directly on your VPS without additional subscription costs. This is especially great for low budget or small hobby projects.&lt;/p&gt;

&lt;p&gt;My demo specs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPS: &lt;a href="https://www.hetzner.com/cloud" rel="noopener noreferrer"&gt;Hetzner Cloud CPX11 (AMD)&lt;/a&gt; - €4,35/month&lt;/li&gt;
&lt;li&gt;Coolify v4.0.0-beta.297&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update: ARM might be &lt;a href="https://github.com/coollabsio/coolify/issues/2088#issuecomment-2082265491" rel="noopener noreferrer"&gt;a faster choice&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install selfhosted Coolify on Hetzner VPS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Just create a new CPX11 cloud server on hetzner, connect via SSH (&lt;code&gt;ssh root@your-server-ip&lt;/code&gt;) and run the command from &lt;a href="https://coolify.io/self-hosted:" rel="noopener noreferrer"&gt;https://coolify.io/self-hosted:&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://cdn.coollabs.io/coolify/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's really it. After install, you can create your admin user via &lt;code&gt;http://your-vps-server-ip:8000/&lt;/code&gt;. See this slightly older YouTube video for full example: &lt;a href="https://www.youtube.com/watch?v=Jg6SWqyvYys" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=Jg6SWqyvYys&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare the SvelteKit demo app
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;You can also use my demo repository: &lt;a href="https://github.com/mandrasch/sveltekit-demo-app-adapter-node" rel="noopener noreferrer"&gt;https://github.com/mandrasch/sveltekit-demo-app-adapter-node&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Create a git repository, use the official SvelteKit installer, select demo app when asked: &lt;code&gt;npm create svelte@latest .&lt;/code&gt; Install dependencies via &lt;code&gt;npm i&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Important: Install adapter-node via &lt;code&gt;npm i -D @sveltejs/adapter-node&lt;/code&gt; and switch from &lt;code&gt;adapter-auto&lt;/code&gt; to &lt;code&gt;adapter-node&lt;/code&gt; in &lt;code&gt;svelte.config.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;adapter&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;@sveltejs/adapter-node&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;kit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;adapter&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;This is needed for SSR support on Coolify. See SvelteKit docs for more information on adapter-node: &lt;a href="https://kit.svelte.dev/docs/adapter-node" rel="noopener noreferrer"&gt;https://kit.svelte.dev/docs/adapter-node&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Commit and push all your changes to your GitHub repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new project in Coolify
&lt;/h2&gt;

&lt;p&gt;Let's get the party started by creating a new project:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F2fqg3gwnn596roufba04.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.amazonaws.com%2Fuploads%2Farticles%2F2fqg3gwnn596roufba04.png" alt="Image description" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select production:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fvzizlo16mlcqzp4kghiw.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.amazonaws.com%2Fuploads%2Farticles%2Fvzizlo16mlcqzp4kghiw.png" alt="Image description" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a new resource:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fmrfitz9bfn702dsjquz0.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.amazonaws.com%2Fuploads%2Farticles%2Fmrfitz9bfn702dsjquz0.png" alt="Image description" width="800" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select "public repository":&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F2lirwkhqnmso1wn6x9h9.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.amazonaws.com%2Fuploads%2Farticles%2F2lirwkhqnmso1wn6x9h9.png" alt="Image description" width="800" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select localhost and destination, there is only one option to select anyway ;-)&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F1nbczjlxc6kxiwpy7ra4.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.amazonaws.com%2Fuploads%2Farticles%2F1nbczjlxc6kxiwpy7ra4.png" alt="Image description" width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F08swz5z2pfvmnrrdtyay.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.amazonaws.com%2Fuploads%2Farticles%2F08swz5z2pfvmnrrdtyay.png" alt="Image description" width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste your repository URL, I used my repository &lt;a href="https://github.com/mandrasch/sveltekit-demo-app-adapter-node:" rel="noopener noreferrer"&gt;https://github.com/mandrasch/sveltekit-demo-app-adapter-node:&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fbj3xisnh4ktrg79qufyc.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.amazonaws.com%2Fuploads%2Farticles%2Fbj3xisnh4ktrg79qufyc.png" alt="Image description" width="800" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "check" and continue with these default settings: &lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fvo6b63e5sux7ticpxifj.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.amazonaws.com%2Fuploads%2Farticles%2Fvo6b63e5sux7ticpxifj.png" alt="Image description" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we configure the install, build and start command. Use the following commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install: &lt;code&gt;npm ci&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build: &lt;code&gt;npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Start: &lt;code&gt;node build&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Frefyqxqimdiyc2kthhml.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.amazonaws.com%2Fuploads%2Farticles%2Frefyqxqimdiyc2kthhml.png" alt="Image description" width="800" height="715"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Important: Don't forget to hit "Save" (!)&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fd72hxpewvl4iemufk3bg.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.amazonaws.com%2Fuploads%2Farticles%2Fd72hxpewvl4iemufk3bg.png" alt="Image description" width="660" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You could also use &lt;code&gt;npm ci --omit dev&lt;/code&gt;, but that only works if you really don't need any of your dev dependencies. See SvelteKit docs for more information: &lt;a href="https://kit.svelte.dev/docs/adapter-node" rel="noopener noreferrer"&gt;https://kit.svelte.dev/docs/adapter-node&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Switch to https and set ORIGIN
&lt;/h2&gt;

&lt;p&gt;The deployment with &lt;code&gt;http://&lt;/code&gt; would be okay, but two important things are missing: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;https support, otherwise cookies won't work&lt;/li&gt;
&lt;li&gt;setting the ORIGIN (via environment variables)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For https, just switch the temporary sslip.io domain to https:// and hit 'Save'(!):&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F89punjobcymgwvd3j4go.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.amazonaws.com%2Fuploads%2Farticles%2F89punjobcymgwvd3j4go.png" alt="Image description" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we need to set the &lt;code&gt;ORIGIN&lt;/code&gt;, otherwise SvelteKit can't really detect its own domain - this will likely cause &lt;code&gt;Cross-site POST form submissions are forbidden&lt;/code&gt; errors.&lt;/p&gt;

&lt;p&gt;Important: The node-adapter docs state that you can use &lt;code&gt;ORIGIN=... node build&lt;/code&gt; as start command, but this did not work on Coolify (guess because of Docker).&lt;/p&gt;

&lt;p&gt;Instead add a new env variable here (!):&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fhtu8cqa123v28to5yzmq.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.amazonaws.com%2Fuploads%2Farticles%2Fhtu8cqa123v28to5yzmq.png" alt="Image description" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Ftch0ek13trrtxdt3w95z.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.amazonaws.com%2Fuploads%2Farticles%2Ftch0ek13trrtxdt3w95z.png" alt="Image description" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it's time for the first deployment, just hit the "Deploy" button and use "Show debug logs" for all information: &lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fjoq5y1o4joc2421k2oei.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.amazonaws.com%2Fuploads%2Farticles%2Fjoq5y1o4joc2421k2oei.png" alt="Image description" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't get confused by red error messages, I guess these are false positives:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fqem460krw4uy1v5xdqiv.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.amazonaws.com%2Fuploads%2Farticles%2Fqem460krw4uy1v5xdqiv.png" alt="Image description" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this is finished, the status in Coolify should switch to "Running" and you should able to play Sverdle (which is implemented via SSR) on your newly created site 🎉&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fecf1phy40vu579p92y32.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.amazonaws.com%2Fuploads%2Farticles%2Fecf1phy40vu579p92y32.png" alt="Image description" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add swap space to prevent "out of memory"
&lt;/h2&gt;

&lt;p&gt;I sometimes ran into "502 Bad Gateway" and 200% CPU load after hitting "Deploy" - while I had other (PHP + MySQL) resources running on my Coolify instance. I asked the Coolify Developer on GitHub and he gave me &lt;a href="https://github.com/coollabsio/coolify/issues/2088" rel="noopener noreferrer"&gt;multiple suggestions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I decided to try the swap option - Swap is &lt;strong&gt;disabled&lt;/strong&gt; by default on Hetzner VPS (see: &lt;a href="https://www.reddit.com/r/hetzner/comments/125hqf4/why_comes_the_default_linux_image_without_swap/" rel="noopener noreferrer"&gt;reddit discussion&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I followed this guide (for ubuntu) and added 6GB swap space (since I only had 17G left on my small VPS) &lt;a href="https://www.digitalocean.com/community/tutorial-collections/how-to-add-swap-space" rel="noopener noreferrer"&gt;https://www.digitalocean.com/community/tutorial-collections/how-to-add-swap-space&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Multiple projects running + builds at the same time now seem to work fine now. 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect a real domain
&lt;/h2&gt;

&lt;p&gt;You can set the domain of your coolify instance in Settings, just point an A-Record to your servers IP.&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2Fb2bp9lavru8tgkqf9xze.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.amazonaws.com%2Fuploads%2Farticles%2Fb2bp9lavru8tgkqf9xze.png" alt="Image description" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also point &lt;code&gt;*.my-coolify-server.example.com&lt;/code&gt; to your server and use this as wildcard for project URLs. &lt;/p&gt;

&lt;p&gt;Let's encrypt HTTPS certificates are managed automatically by Coolify - but only if your domain is in "Domains", see below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use www- and non-www domain / redirect&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to use both www and non-www domain, you need to set them both here - otherwise the letsencrypt certificate is not generated for both variants.&lt;/p&gt;

&lt;p&gt;Here I use two domains and redirect www- to non-www:&lt;/p&gt;

&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.amazonaws.com%2Fuploads%2Farticles%2F0gkijeoibln70zyvi8yx.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.amazonaws.com%2Fuploads%2Farticles%2F0gkijeoibln70zyvi8yx.png" alt="Image description" width="800" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't forget to hit the "Set direction" button as well. See this for more information: &lt;a href="https://coolify.io/docs/knowledge-base/traefik/redirects" rel="noopener noreferrer"&gt;https://coolify.io/docs/knowledge-base/traefik/redirects&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://coolify.io/docs/installation#requirements" rel="noopener noreferrer"&gt;Coolify requirements&lt;/a&gt; for more information. The good thing is that you can always upscale/downscale the resources for Hetzner Cloud VPS (as far as I know).&lt;/p&gt;

&lt;p&gt;Have fun with selfhosting!&lt;/p&gt;

&lt;p&gt;Disclaimer: If you want to use this setup in production, please take measures to secure your VPS like adding a firewall on hetzner. See e.g. &lt;a href="https://dev.to/code42cate/5-easy-steps-to-secure-your-cloud-server-i17"&gt;5 Easy Steps to Secure your Cloud Server 🔒&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docs and more resources**
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://coolify.io/docs/resources/applications/svelte-kit" rel="noopener noreferrer"&gt;https://coolify.io/docs/resources/applications/svelte-kit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kit.svelte.dev/docs/adapter-node" rel="noopener noreferrer"&gt;https://kit.svelte.dev/docs/adapter-node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nixpacks.com/docs/providers/node" rel="noopener noreferrer"&gt;https://nixpacks.com/docs/providers/node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Coolify crash course &lt;a href="https://www.youtube.com/watch?v=taJlPG82Ucw" rel="noopener noreferrer"&gt;Self Host 101 - Set up Coolify | Self Hosted PaaS with Zero Config Deployments&lt;/a&gt; (YouTube)&lt;/li&gt;
&lt;li&gt;General tutorial: &lt;a href="https://sreyaj.dev/deploy-nodejs-applications-on-a-vps-using-coolify" rel="noopener noreferrer"&gt;Deploy Node.js applications on a VPS using Coolify&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>sveltekit</category>
      <category>coolify</category>
      <category>hosting</category>
      <category>vps</category>
    </item>
    <item>
      <title>Install Craft CMS v4 with one command via DDEV</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Wed, 24 Jan 2024 19:54:46 +0000</pubDate>
      <link>https://forem.com/mandrasch/install-craft-cms-v4-with-one-command-via-ddev-32da</link>
      <guid>https://forem.com/mandrasch/install-craft-cms-v4-with-one-command-via-ddev-32da</guid>
      <description>&lt;p&gt;2023 was another great year for Craft CMS: &lt;a href="https://craftcms.com/blog/2023-in-review"&gt;2023 in Review&lt;/a&gt;. There are now  over 8'000 developers registered in &lt;a href="https://discord.com/invite/uuDFCTX"&gt;Craft Discord&lt;/a&gt; according to the latest &lt;a href="https://craftcms.com/events/dot-all-2023/sessions/state-of-craft-2023"&gt;State of Craft&lt;/a&gt; talk by Brandon Kelly.&lt;/p&gt;

&lt;p&gt;Do you want to try it quickly?&lt;/p&gt;

&lt;p&gt;Looking for &lt;a href="https://dev.to/mandrasch/install-craft-cms-v5-alpha-with-one-command-via-ddev-4ne5"&gt;Craft v5&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you haven't installed a Docker runtime, you might be happy with &lt;a href="https://orbstack.dev/"&gt;Orbstack&lt;/a&gt;. Other alternatives: &lt;a href="https://ddev.readthedocs.io/en/stable/users/install/docker-installation/"&gt;DDEV docs: Docker installation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Afterwards you can simply install DDEV via homebrew (on Mac):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install ddev/ddev/ddev &amp;amp;&amp;amp; mkcert -install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check if it was successful with &lt;code&gt;ddev -v&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For other operating systems see &lt;a href="https://ddev.readthedocs.io/en/latest/users/install/ddev-installation/"&gt;DDEV docs&lt;/a&gt; for setup instructions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install CraftCMS v4&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get the latest CraftCMS v4 version with this one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir craft4-ddev &amp;amp;&amp;amp; \ 
  cd craft4-ddev &amp;amp;&amp;amp; \
  ddev config \
    --project-type=craftcms \
    --docroot=web \
    --create-docroot \
    --php-version="8.2" \
    --database="mysql:8.0" \
    --nodejs-version="20" &amp;amp;&amp;amp; \
  ddev start -y &amp;amp;&amp;amp; \
  ddev composer create -y --no-scripts --no-interaction "craftcms/craft:^4" &amp;amp;&amp;amp; \
  ddev craft install/craft \
    --username=admin \
    --password=password123 \
    --email=admin@example.com \
    --site-name=Testsite \
    --language=en \
    --site-url='$DDEV_PRIMARY_URL' &amp;amp;&amp;amp; \
  echo 'Nice, ready to launch!' &amp;amp;&amp;amp; \
  ddev launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be greeted with the default index template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfct5sqks42zknvvi73h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfct5sqks42zknvvi73h.png" alt="Image description" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also open the backend via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ddev launch /admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and login via &lt;code&gt;admin&lt;/code&gt; and &lt;code&gt;password123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3h2ywkmt9p3pfpsxx4ft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3h2ywkmt9p3pfpsxx4ft.png" alt="Craft login" width="800" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automate it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can take this one step further and generate the folder (and therefore project names) dynamically. Thanks very much to &lt;a href="https://discord.com/channels/456442477667418113/1199815065327632554/1199821775861710848"&gt;August Miller&lt;/a&gt; for this recommendation!&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="nv"&gt;TIMESTAMP_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s1"&gt;'%Y%m%d-%H%M'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="s2"&gt;"craft4-&lt;/span&gt;&lt;span class="nv"&gt;$TIMESTAMP_FOLDER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"craft4-&lt;/span&gt;&lt;span class="nv"&gt;$TIMESTAMP_FOLDER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
ddev config &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--project-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;craftcms &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--docroot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--create-docroot&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--php-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"8.2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mysql:8.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--nodejs-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
ddev start &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
ddev composer create &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-scripts&lt;/span&gt; &lt;span class="nt"&gt;--no-interaction&lt;/span&gt; &lt;span class="s2"&gt;"craftcms/craft:^4"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
ddev craft &lt;span class="nb"&gt;install&lt;/span&gt;/craft &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password123 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin@example.com &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--site-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Testsite &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--site-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'$DDEV_PRIMARY_URL'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Nice, ready to launch!'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
ddev launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But beware to not fill up your local harddrive too much. Delete DDEV projects with &lt;code&gt;ddev delete&lt;/code&gt; (or &lt;code&gt;ddev delete -O&lt;/code&gt; without snapshot) and keep your projects organized with &lt;code&gt;ddev list&lt;/code&gt;. You can also use &lt;code&gt;ddev delete -O &amp;lt;project-name&amp;gt;&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it out online with GitHub Codespaces&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you don't want to install Craft CMS v4 locally, you could also launch a new Github Codespace instance here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mandrasch/launch-codespace-with-ddev"&gt;https://github.com/mandrasch/launch-codespace-with-ddev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsu45ujqawdj9aib08voa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsu45ujqawdj9aib08voa.png" alt="Screenshot launch codespace" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste the command from above into the terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xilsogbwkn33q1qklov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xilsogbwkn33q1qklov.png" alt="Screenshot codespaces terminal" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open port 8080 (web) in the simple browser or in a new browser tab in the ports section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i2990dp5e74tne05xjs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i2990dp5e74tne05xjs.png" alt="Screenshot codespaces with Craft CMS open" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have fun trying out Craft CMS!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://craftcms.com/docs/getting-started-tutorial/"&gt;Welcome to Craft - official tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.com/invite/uuDFCTX"&gt;Craft CMS Discord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ddev</category>
      <category>php</category>
      <category>cms</category>
      <category>craftcms</category>
    </item>
    <item>
      <title>Install Craft CMS v5 with one command via DDEV</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Sun, 07 Jan 2024 13:03:30 +0000</pubDate>
      <link>https://forem.com/mandrasch/install-craft-cms-v5-alpha-with-one-command-via-ddev-4ne5</link>
      <guid>https://forem.com/mandrasch/install-craft-cms-v5-alpha-with-one-command-via-ddev-4ne5</guid>
      <description>&lt;p&gt;The new major version 5 of Craft CMS will introduce a lot of new features and improvements. See all changes here: &lt;a href="https://craftcms.com/blog/craft-5-beta-released" rel="noopener noreferrer"&gt;Craft 5 Beta Released&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Do you already want to try the new version? It is super simple with &lt;a href="https://ddev.com/" rel="noopener noreferrer"&gt;DDEV&lt;/a&gt; - just paste one command into your terminal!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update: Craft5 is released - &lt;a href="https://craftcms.com/blog/craft-5" rel="noopener noreferrer"&gt;https://craftcms.com/blog/craft-5&lt;/a&gt;. The commands in this article are updated for the latest stable v5 version 🎉&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Craft CMS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Craft CMS is a flexible Content Management System with an awesome authoring experience (AX) as well as powerful features for developers and designers alike. 2023 was another great year for Craft CMS: &lt;a href="https://craftcms.com/blog/2023-in-review" rel="noopener noreferrer"&gt;2023 in Review&lt;/a&gt;. There are now  over 8'000 developers registered in &lt;a href="https://discord.com/invite/uuDFCTX" rel="noopener noreferrer"&gt;Craft Discord&lt;/a&gt; - according to the latest &lt;a href="https://craftcms.com/events/dot-all-2023/sessions/state-of-craft-2023" rel="noopener noreferrer"&gt;State of Craft&lt;/a&gt; talk by Brandon Kelly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you haven't installed a Docker runtime, you might be happy with &lt;a href="https://orbstack.dev/" rel="noopener noreferrer"&gt;Orbstack&lt;/a&gt;. Other alternatives: &lt;a href="https://ddev.readthedocs.io/en/stable/users/install/docker-installation/" rel="noopener noreferrer"&gt;DDEV docs: Docker installation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Afterwards you can simply install DDEV via homebrew (on Mac):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

brew install ddev/ddev/ddev &amp;amp;&amp;amp; mkcert -install


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Check if it was successful with &lt;code&gt;ddev -v&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For other operating systems see &lt;a href="https://ddev.readthedocs.io/en/latest/users/install/ddev-installation/" rel="noopener noreferrer"&gt;DDEV docs&lt;/a&gt; for setup instructions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install CraftCMS v5&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get the latest CraftCMS v5 version with this one command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

mkdir craft5-ddev &amp;amp;&amp;amp; \ 
  cd craft5-ddev &amp;amp;&amp;amp; \
  ddev config \
    --project-type=craftcms \
    --docroot=web \
    --create-docroot \
    --php-version="8.2" \
    --database="mysql:8.0" \
    --nodejs-version="20" &amp;amp;&amp;amp; \
  ddev start -y &amp;amp;&amp;amp; \
  ddev composer create -y --no-scripts craftcms/craft &amp;amp;&amp;amp; \
  ddev craft install/craft \
    --username=admin \
    --password=password123 \
    --email=admin@example.com \
    --site-name=Testsite \
    --language=en \
    --site-url='$DDEV_PRIMARY_URL' &amp;amp;&amp;amp; \
  echo 'Nice, ready to launch!' &amp;amp;&amp;amp; \
  ddev launch


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You will be greeted with the default index template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfct5sqks42zknvvi73h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfct5sqks42zknvvi73h.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also open the backend via&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

ddev launch /admin


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and login via &lt;code&gt;admin&lt;/code&gt; and &lt;code&gt;password123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3h2ywkmt9p3pfpsxx4ft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3h2ywkmt9p3pfpsxx4ft.png" alt="Craft login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automate it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can take this one step further and generate the folder (and therefore project names) dynamically. Thanks very much to &lt;a href="https://discord.com/channels/456442477667418113/1199815065327632554/1199821775861710848" rel="noopener noreferrer"&gt;August Miller&lt;/a&gt; for this recommendation!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nv"&gt;TIMESTAMP_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s1"&gt;'%Y%m%d-%H%M'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="s2"&gt;"craft5-&lt;/span&gt;&lt;span class="nv"&gt;$TIMESTAMP_FOLDER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"craft5-&lt;/span&gt;&lt;span class="nv"&gt;$TIMESTAMP_FOLDER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
ddev config &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--project-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;craftcms &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--docroot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;web &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--create-docroot&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--php-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"8.2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mysql:8.0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--nodejs-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"20"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ddev start &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ddev composer create &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-scripts&lt;/span&gt; craftcms/craft &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ddev craft &lt;span class="nb"&gt;install&lt;/span&gt;/craft &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;password123 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin@example.com &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--site-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Testsite &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--site-url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'$DDEV_PRIMARY_URL'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Nice, ready to launch!'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  ddev launch


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;But beware to not fill up your local harddrive too much. Delete DDEV projects with &lt;code&gt;ddev delete&lt;/code&gt; (or &lt;code&gt;ddev delete -O&lt;/code&gt; without snapshot) and keep your projects organized with &lt;code&gt;ddev list&lt;/code&gt;. You can also use &lt;code&gt;ddev delete -O &amp;lt;project-name&amp;gt;&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it out online with GitHub Codespaces&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you don't want to install Craft CMS v5 locally, you could also launch a new Github Codespace instance here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mandrasch/launch-codespace-with-ddev" rel="noopener noreferrer"&gt;https://github.com/mandrasch/launch-codespace-with-ddev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsu45ujqawdj9aib08voa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsu45ujqawdj9aib08voa.png" alt="Screenshot launch codespace"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste the command from above into the terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xilsogbwkn33q1qklov.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1xilsogbwkn33q1qklov.png" alt="Screenshot codespaces terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open port 8080 (web) in the simple browser or in a new browser tab in the ports section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i2990dp5e74tne05xjs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i2990dp5e74tne05xjs.png" alt="Screenshot codespaces with Craft CMS open"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have fun trying Craft CMS v5 out!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://craftcms.com/docs/getting-started-tutorial/" rel="noopener noreferrer"&gt;Welcome to Craft - official tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://craftquest.io/courses/craft-5-guide" rel="noopener noreferrer"&gt;craftquest.io: Craft 5 guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nystudio107/spin-up-craft" rel="noopener noreferrer"&gt;spin-up-craft (without DDEV, Docker native)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.com/invite/uuDFCTX" rel="noopener noreferrer"&gt;Craft CMS Discord&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Want to try CraftCMS stable with one command?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mandrasch/install-craft-cms-v4-with-one-command-via-ddev-32da"&gt;https://dev.to/mandrasch/install-craft-cms-v4-with-one-command-via-ddev-32da&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ddev</category>
      <category>craftcms</category>
      <category>php</category>
      <category>docker</category>
    </item>
    <item>
      <title>Rive: Better collaboration between designers and developers on interactive animations?</title>
      <dc:creator>Matthias Andrasch</dc:creator>
      <pubDate>Sun, 24 Dec 2023 11:21:23 +0000</pubDate>
      <link>https://forem.com/mandrasch/rive-better-collaboration-between-designers-and-developers-on-interactive-animations-8em</link>
      <guid>https://forem.com/mandrasch/rive-better-collaboration-between-designers-and-developers-on-interactive-animations-8em</guid>
      <description>&lt;p&gt;How do interactive designers work hand in hand with web developers? This seems to be a tough nut to crack since Adobe Flash was discontinued. &lt;/p&gt;

&lt;p&gt;Rive promises a new way of enabling collaboration between design and development with their State Machine approach: Designers are able to create states and events for their animation - and developers can trigger these from outside (via JavaScript). 🤝  &lt;/p&gt;

&lt;p&gt;Here is a good, brief example to trigger a state via JS (react) by &lt;a href="https://marmelab.com/blog/2023/01/30/rive-animation-state-machine.html"&gt;Anthony Rimet&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isHandsUpInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StateMachineInput&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStateMachineInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;riveInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;STATE_MACHINE_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;hands_up&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="c1"&gt;// ...&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
    &lt;span class="nx"&gt;onFocus&lt;/span&gt;&lt;span class="o"&gt;=&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="nx"&gt;isHandsUpInput&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="nx"&gt;onBlur&lt;/span&gt;&lt;span class="o"&gt;=&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="nx"&gt;isHandsUpInput&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9TSZECZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wm1hki3fis4zha70tlm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9TSZECZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wm1hki3fis4zha70tlm.png" alt="Character animation which is animated when password input is selected" width="561" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DB1aZpC9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yapa69jhd51h02sj7cog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DB1aZpC9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yapa69jhd51h02sj7cog.png" alt="Character animation which is animated when password input is selected - second screenshot" width="800" height="940"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full tutorial: &lt;a href="https://marmelab.com/blog/2023/01/30/rive-animation-state-machine.html"&gt;"Rive: Animate Web UIs with State Machines" - Anthony Rimet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this podcast episode, one of the Rive founders - Guido Rosso - shares their vision: "Is this the new Flash? Rive and the future of interactive design" - School of Motion Podcast&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/U0Jd5-eStu4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spotify &lt;a href="https://open.spotify.com/episode/64pOVsEV32RAcgHJo4sSIt"&gt;https://open.spotify.com/episode/64pOVsEV32RAcgHJo4sSIt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Intro To Interactive Animation In Rive | UI Animation Tool" - School of Motion:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/yaP07L5J50E"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A comparison between Rive and Lottie (by Rive): &lt;a href="https://rive.app/blog/rive-as-a-lottie-alternative"&gt;https://rive.app/blog/rive-as-a-lottie-alternative&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Although it has to be said that Lottie is an Open Source animation format whereas Rives file format is proprietary. Only the Rive runtimes are Open Source.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Check out Rive here: &lt;a href="https://rive.app/"&gt;https://rive.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's see how this will play out in 2024. 🪄&lt;/p&gt;

&lt;p&gt;Thanks very much to motion designer Olga Vikhrova for sharing the podcast episode about Rive with me - check out Olgas amazing work here: &lt;a href="https://www.instagram.com/olga_a_vikhrova/"&gt;https://www.instagram.com/olga_a_vikhrova/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>animation</category>
      <category>javascript</category>
      <category>motiondesign</category>
      <category>rive</category>
    </item>
  </channel>
</rss>
