Migrating a personal homepage to Svelte

Mar 4, 2024 #development#frontend#svelte

Thinking about the Svelte Web Framework? I’m going to describe a 3 fears I had before trying it out and where I landed on Svelte when compared with NextJS for personal homepages.

So, when I'm working on a blog or hobbyist side-project, I usually value minimizing boilerplate code. I’ve really enjoyed React’s way of thinking about components as composable units of code via unidirectional data flow. Server-side rendering pushed me into adopting NextJS (for a bunch of reasons, but one was just having an elegant way to implement meta-tags). NextJS worked pretty well for me, but I was listening to the Syntax podcast share positive things about Svelte. I decided I wanted to try it for this site, a frontend-only static blog.

Three Fears

Based on my previous experiences and the code for this blog, there were three themes I was nervous about Svelte after a quick read about it:

Flexibility

My first fear when reading the Svelte getting started guide was its lack of support for multiple components in one file. I was worried this rule was foreshadowing future inflexibility. As I’ve experimented with it, I’ve hardly been challenged by this limitation. My NextJS components nearly all 1:1 mapped to a page in Svelte. I realized that the only times when I had designed multiple components within a single TSX file were the times when I was destructuring complicated data structures into views. In these cases, I found the components more readable after migrating them to multiple components. Here are examples where I needed to think the hardest:

  • Comments & Sidebar Menus. In React, I defined comments and sidebar menus as data. For example, for Sidebars:

    [{
        name: 'Blog',
        link: '/posts',
        list: getBlogPosts(), // Tree with more data
    },
    {
        name: 'About Jake',
        link: '/about'
    }]
        

    When I went to render them, I recursively walked the tree structure and decomposed the sidebar into React components. (Note: The sidebar could have probably just been rewritten as basic .svelte files, but this was more difficult for my Mastodon Comments system. Solving this was straightforward using dynamic components, such as svelte:self and svelte:component components. I recursively walk the tree using injected-in properties.

  • Blog posts. Each blog post on this website is two things: (1) metadata, such as the title, date it was posted, etc, and (2) content. The content was previously JSX, and I was anxious that migrating to Svelte would require each blog post to be spread across multiple files. There are probably more ways to solve this, but this ended up being trivial for me by defining module-level exported constants that can be imported when the Svelte Component is imported. Here's an example of how that looks:
    // Metadata defined alongside the actual posts, written as .svelte files
    <script context="module">
    export const metadata = {
        static_path: '2024-02-04-svelte',
        title: 'Migrating my Blog to Svelte: Fears and Outcomes',
        tags: ['development', 'frontend', 'svelte'],
        // Dates are zero indexed in javascript
        date: new Date(2024, 2, 2),
    };
    </script>
    <!-- content goes here -->

    I then import that into a global post list with two lines: (I imagine someday I'll generate this file automatically.)

    import { content as p2024m02d22content } from './2024-02-22-svelte.svelte';
    import p2024m02d22 from './2024-02-22-svelte.svelte';
    export const posts = [
    {
      content: p2024m02d22content,
      component: p2024m02d22 as ComponentType<p2024m02d22>
    },
    // ... more posts

Styles

Another fear I had was that it'd be difficult to translate my existing styles to Svelte, and expressing them using Svelte's styling system would require too many compromises. In React, I write many of my styles as using CSS-in-JS, describing them with variables in the same file as the JSX for the components that use them. The styles themselves were defined using Emotion. In the old code, I could easily make the styles more dynamic by just passing values into the style code.

Having dynamic styles was easy to do in Svelte. The main difference was that I had to pass in a var(--parameter) into the CSS. Not only was it easy, but my code was more readable because it forced me to separate the business logic from the rendering. The only caveat about Svelte styles is that there's more boilerplate. I have lines referencing the color variables in the <script>, html, and <style> sections of my .svelte file. I couldn't help but wish there was an easy way to reference the script variables from the CSS file.

Translating my styles to Svelte was easy too. My actual styling code was so close between NextJS and Svelte that I was able to create a very simple ChatGPT prompt to help me do the rote work of translating double-quotes and commas to semicolons. The interaction looked like this (though, with less-trivial code-examples):

Me: Convert the following to Svelte:
const buttonStyle = {
  color:  "#FFF",
  backgroundColor: "#333",
  ":hover": {
    backgroundColor: "#000"
  },
}
ChatGPT: Sure.
.button {
  color: #FFF;
  background-color: #333;
}

.button:hover {
  background-color: #000;
}

Though it occasionally hallucinated, ChatGPT did a better job than a static parser would because it was able to identify opportunities to change things for Svelte that I hadn't considered.

Templating – My final fear was that the .svelte templates would not be as expressive as I needed. I'd often destructure data in-line in JSX in NextJS / React. In Svelte, I found it was simplier to map more complicated objects from within the <script/> section of the .svelte file, which could be easily referenced by the html.

I was happy that Svelte #if statements are sufficiently smart enough to behave as you'd expect, as a trivial example:

{#if value !== undefined}
  {value} <!-- assumed not undefined here. -->
{:else}
  {value} <!-- assumed undefined here. -->
{/if}

Also, I found triaged issues in the Svelte open-source project[1] [2], that gave me the impression that the the Svelte team cares about templating being sufficiently espressive, even if not every problem is solvable by templates.

Outcomes

Ultimately, I had a very positive experience with Svelte. It’s my current favorite web framework for personal projects. I'm optimistic towards using it in future projects. Most of my usage so far has been for frontend-only projects, yet Svelte excels at defining fullstack web applications and I haven't touched on any of those capabilities in this post. Hopefully some of what I’ve written help you know if any of your fears are addressed by this web framework.

So, I’d love to hear from you! What do you care about most in a web framework? Please leave a comment on the Mastodon thread below!

Appendix

A note: I work at Google, on Firebase, I work on mobile developer tools, I don't work on any of the hosting solutions for web developers, but I wanted to disclose this. Both NextJS and Svelte work really well on both Vercel and GCP, though I get a slightly more cloud agnostic vibe from Svelte's adapters, yet my perspective is probably colored by where I work.

What about other Frameworks? I also took a quick exploration of Remix before diving into Svelte. Remix was acquired by Shopify and looks really good. I think it’d be really sensible in a professional setting. I used Emotion to create inline-styles in most of my personal projects in React and this pattern seemed to not be well supported in Remix [1] [2]. Though I'm not using CSS-in-JS in Svelte, the solution to inline your css within each .svelte file felt similar enough in practice to be worth trying.