Rewrite my website with Svelte

The page you’re looking at is powered by Svelte. The previous version of the site was based on Gatsby, and even earlier on Hexo. When I came across Svelte, I was amazed by its features. So i decided to rewrite my site and take the opportunity to dive into Svelte and its ecosystem.

index-desktop

It took me five weekends to finish the rewriting process. Now, I’d like to share what I did.

Framework and Development

SvelteKit is a framework for building web applications. SvelteKit for Svelte is similar to Next.js for React, Nuxt.js for Vue. it offer build tools, filesystem-based routing and rendering practices for us to setup Svelte app quickly.

For development, SvelteKit integrates Vite, which makes both development and production builds extremely fast.

Additionally, I added config to make vite resolve my posts files from project root folder:

JavaScript
// svelte.config.js
    vite: {
      resolve: {
        alias: {
          $draw: path.resolve('./draw'),
          $blog: path.resolve('./blog'),
          $note: path.resolve('./note'),
          $src: path.resolve('./src'),
        }
      },
      // allows vite access to ./posts
      server: {
        fs: {
          allow: ['./']
        }
      },
    }
``

## Routing

With SvelteKit's filesystem-based routing, anything inside `src/routes` directory will be compiled to a page and accessible via its path.

On my website, I have added pages for Index (Home), About, Uses, Blog, and Draws. The Blog page is further divided into post, notes and slides: 
```bash
.
├── about@page.svelte
├── blog
│   ├── [slug]
│   │   └── index@post-page.svelte
│   └── index@page.svelte
├── draw
│   ├── [slug]
│   │   └── index@draw-page.svelte
│   └── index@page.svelte
├── index@page.svelte
├── note
│   ├── [slug]
│   │   └── index@post-page.svelte
│   └── index@page.svelte
├── slide
│   └── index@page.svelte
└── uses.svx

The index@page.svelte files in blog, draw, note and slide folders serve as index pages for all my posts, drawings, notes and slides.

These pages can have a load function that fetches data for my blog posts before the component is created, and it can even run on the server side with prerender opton:

JavaScript
<script context="module">
  export const load = async({ fetch }) => {

    const fethcPosts = await fetch('/api/posts?sorted=true&reading=true');
    const allSortedPosts = await fethcPosts.json();
    return {
      props: {
        allSortedPosts,
      },
    };
  }
</script>

For the post detail pages, it is necessary to use a template to display different pages for each post. Therefore, I utilized dynamic routing with the index@post-page.svelte file wrapped in [slug] folder. The load function in these page only needs to fetch post data based on the slug:

JavaScript
<script context="module">
  export const load = async({ params, fetch }) => {
    const { slug } = params;

    const fethcPost = await fetch(`/api/post/${slug}`);
    const post = await fethcPost.json();

    if (!post) {
      return {
        status: 404,
        error: 'Post not found'
      }
    };

    return {
      props: {
        post,
      },
    };
  }
</script>

Layout

SvelteKit supports filesystem-based routing, allowing us to setup layouts, including nested and named layouts.

I added the following 4 layout files:

Bash
├── __layout-draw-page@default.svelte
├── __layout-page@default.svelte
├── __layout-post-page@default.svelte
├── __layout.svelte

__layout.svelte serves as the root layout for my website, defining the main structure of the pages:

HTML, XML
<Statistic />
<ThemeContext>
  <Header {pathname} />
  <div id="app-container" class="container">
    <slot />
  </div>
  <Footer />
</ThemeContext>

The other three files are named layouts, inherited directly from the __layout.svelte.

__layout-draw-page@default.svelte and __layout-post-page@default.svelte are layouts for blog detail pages.

__layout-page@default.svelte layouts the other pages.

You can see i used names page, draw-page and post-page for different layouts, which correspond to different page files such as index@post-page.svelte and about@page.svelte.

Styling and Typography

I prefer styling with Less and PostCSS. I like the feeling of writing original CSS Code rather than Atomic or Utility-first CSS like Tailwind CSS. As svelte-preprocess can automatically transforms the code to provide support for PostCSS and Less, I configure the svelte.config.js:

JavaScript
import sveltePreprocess from "svelte-preprocess";
...
const config = {
  preprocess: [
    sveltePreprocess({
      postcss: true,
    }),
    ...
  ],
};
export default config;

Post page with Markdown

To render Markdown files, i used mdsvex, which converts Markdown to HTML, supports using Markdown files as component, and allows components to be used within Markdown files via .svx file.

You can see i added uses.svx , combining svelte components, html and markdown syntax:

HTML, XML
<Seo {...seoProps} />


<div class="uses-page content">

# Uses

## Hardware

For normal markdown files, I configured mdsvex into svelte-preprocess in svelte.config.js:

JavaScript
//svelte.config.js
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
...
preprocess: [
  ...
  mdsvex({
    extensions: ['.md', '.svelte', '.svx'],
    highlight: {
      highlighter
    },
    rehypePlugins: [
      [
        rehypeUrls, 
        processUrl
      ],
      rehypeSlug,
      rehypeAutolinkHeadings,
    ],
  }),
]

rehype-slug plugin can add id attributes to headings, rehype-autolink-headings plugin add links from headings back to themselves. They help me to implement hash anchor for headings.

I customized rehype-urls plugin to fill the empty alt attribute for img tag, the target and rel attributes for outside a tag, and specified all the images should be in webp format:

JavaScript
function processUrl(url, node) {
  if (node.tagName === 'a') {
    if (!node.properties.alt) {
      node.properties.alt = 'img';
    }
    if (!url.href.startsWith('/')) {
      node.properties.target = '_blank';
      node.properties.rel = 'noreferrer external';
    }
  }
  if (node.tagName === 'img') {
    return `${node.properties.src}!/format/webp`;
  }
}

Rss

I used an endpoint, resolving all my posts and serving a XML file, to implement RSS feed.

JavaScript
// rss.xml.js

export const get = async () => {
  const sortedPosts = await getAllPosts({ sorted: true });

  const headers = {
    'Cache-Control': 'max-age=0, s-max-age=3600',
    'Content-Type': 'application/xml',
  };

  const body = xml(sortedPosts);

  return {
    headers,
    body,
  };
}

SSG

SSG means Static Site Generation, it is ideal for pre-rendering every page on my website.

SvelteKit provides several adapters, and I chose the static adapter for this purpose:

JavaScript
// svelte.config.js
import adapter from '@sveltejs/adapter-static'

...
kit: {
  adapter: adapter(),
    prerender: {
      default: true,
    },
  ...
}

Sitemap

To make a better performance for SEO, it is important to has an auto generated sitemap.

After using the static-adapter above, i can only generate sitemap files after the whole site was built.

I added a sitemap script in package.json:

JSON
// package.json
  "scripts": {
    "sitemap": "node --experimental-json-modules ./gen-sitemap.js",
  }

And a gen-sitemap.js to read all build files and trimming files url to build sitemap:

JavaScript
import fs from 'fs';
import fg from 'fast-glob';
import { create } from 'xmlbuilder2';
import globalConfig from './global-config.js';

const getUrl = (url) => {
    const trimmed = url.slice(6).replace('index.html', '');
    return `${globalConfig.siteURL}/${trimmed}`;
};

async function createSitemap() {
    const sitemap = create({ version: '1.0' }).ele('urlset', {
        xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9'
    });

    const pages = await fg(['build/**/*.html']);

    pages.forEach((page) => {
      const url = sitemap.ele('url');
      url.ele('loc').txt(getUrl(page));
    });

    const xml = sitemap.end({ prettyPrint: true });

    fs.writeFileSync('build/sitemap.xml', xml);
}

createSitemap();

Thanks

That’s what I did with Svelte and SvelteKit for this new website. Hope it can give you some ideas.

Thank you for reading.