Rewrite my website with Svelte

It is Svelte that the page you’re looking at is powered by. The previous version of it was based on Gatsby, and on Hexo if we keep going ago. But when i came across Svelte, i was amazed at it’s features. So i decided to rewrite my site and take chance to dive into Svelte and it’s ecosystem.

index-desktop

To finished the rewriting process took me five weekends. Now, i will talk about what i did.

Framework and Development

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

For Development, SvelteKit integrated Vite, which make both dev and production builds extremely fast.

And I added config to make vite resolve my posts files form 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 filesystem-based routing, anything inside `src/routes` will be compiled to a page and accessible via its path.

I added Index (Home), About, Uses, Blog and Draws pages in my website. Blog Page was also 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 are index pages for all my posts, draws, notes and slides.

These pages can have a load function, which can fetching my blog posts data before component is created, even on 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 post detail page, it is necessary to use a template to dispaly all different pages. So i used dynamic routing. That is the index@post-page.svelte file wrapped in [slug] folder. Load function in these page only need to fetching post data via 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 allows us to set layouts file, even Nested layouts 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 is my root layout, which layout the main structure of my website 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, and reference different names in different page file, such as index@post-page.svelte, about@page.svelte.

Styling and Typography

I prefer to styling with Less and PostCSS. I was satisfied with 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, so 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. It can convert markdown to HTML, allows markdown files to be used as component, even allow component to be used inside markdown with 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, we need to add mdsvex into svelte-preprocess:

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. So we can used them 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. Also, i specified all the images as 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. I thought it is suitable for my website to pre-render every page.

SvelteKit provide several adapters, and i chose static adapter, which can pre-render every page as static:

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.

But After i used static-adapter above, i can only generate sitemap files after the whole site was build.

I added a sitemap script:

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 is what i did with Svelte and SvelteKit for this new website. Hope it can give you some ideas.

Thank you for reading.