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.
To finished the rewriting process took me five weekends. Now, i will talk about what i did.
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:
// 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:
<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:
<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>
SvelteKit allows us to set layouts file, even Nested layouts and Named layouts.
I added the following 4 layout files:
├── __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:
<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
.
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
:
import sveltePreprocess from "svelte-preprocess";
...
const config = {
preprocess: [
sveltePreprocess({
postcss: true,
}),
...
],
};
export default config;
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:
<Seo {...seoProps} />
<div class="uses-page content">
# Uses
## Hardware
For normal markdown files, we need to add mdsvex into svelte-preprocess:
//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:
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`;
}
}
I used an endpoint, resolving all my posts and serving a XML file, to implement RSS feed.
// 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 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:
// svelte.config.js
import adapter from '@sveltejs/adapter-static'
...
kit: {
adapter: adapter(),
prerender: {
default: true,
},
...
}
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:
// 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:
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();
That is what i did with Svelte and SvelteKit for this new website. Hope it can give you some ideas.
Thank you for reading.