This page looks best with JavaScript enabled

Create a blog on Sveltekit

 ·   ·  ☕ 10 min read

In this post let us create a simple blog using Sveltekit.

Why Sveltekit if you ask -

  1. Because it’s cool
  2. Make your pages interactive without a big JS payload for the client
  3. Fast

Get started with the following command in your favorite terminal -

1
pnpm create svelte@latest avix-blog-sveltekit

This should initiate the folder with basic sveltekit structure and initiate a few files.

create-sveltekit-app-blog

Choose Skeleteon project as an option and select/de-select Javascript, Prettier and ESLint options based on your preferences.

Install dependencies, open the project in an editor of your choice and run the project.

1
2
3
4
cd avix-blog-sveltekit
pnpm i
code .
pnpm run dev

Navigate to http://localhost:5173/ to see this beautiful page.

sveltekit-skeleton-starting-page

We will use a small CSS library called chota - not fussing on styling in this example.

Include the below line in src/app.html -

1
<link rel="stylesheet" href="https://unpkg.com/chota@latest">

Routing and Static Pages

Sveltekit enables easy and dynamic routing.

  • Any page within src/routes folder is a route in the app
  • Define the page content using +page.svelte within the specific route folder

Create an about route -

  1. Create a folder src/routes/about
  2. Create a file called src/routes/about/+page.svelte

Use below content for the new +page.svelte -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<h1>
  About
</h1>

<b>This is the greatest blog that has ever been written</b>

<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam hendrerit nisi sed sollicitudin pellentesque. Nunc posuere purus rhoncus pulvinar aliquam. Ut aliquet tristique nisl vitae volutpat. Nulla aliquet porttitor venenatis. Donec a dui et dui fringilla consectetur id nec massa. Aliquam erat volutpat. 

Sed ut dui ut lacus dictum fermentum vel tincidunt neque. Sed sed lacinia lectus. Duis sit amet sodales felis. Duis nunc eros, mattis at dui ac, convallis semper risus. In adipiscing ultrices tellus, in suscipit massa vehicula eu.
</p>

The convention of using + in file names is picked up by Sveltekit.

  1. We have already established that about folder signifies a route. +page.svelte in that folder is the “root page” that gets rendered when you go to example.com/about. This page gets server rendered for the first request, and rendered in client for subsequent requests
  2. Other files of interest in the same about folder (all files optional) -
  • +page.js - Do stuff before +page.svelte gets rendered
  • +page.server.js - Run this JS only on server (e.g. fetch data from DB)
  • +error.svelte - Custom error page
  1. See the sveltekit routing docs to understand more on the different file name conventions

While I have nothing but admiration for the thinking that has gone in the routing structure, I was happier with the older, simpler file structure.

Create another file called blog/+page.svelte with the below content -

1
2
3
<h1>Blog</h1>
<p>My blog will be written here.</p>

Visit http://localhost:5173/about and http://localhost:5173/blog to see the pages.

sveltekit-blog-about-page

While this content is great and everything, the pages need a bit more work to bring consistent styling and to avoid using HTML for every new post.

Create layouts

Layouts help us define styling and structure at a root level, or for every page.

Get started with a common layout that is applicable for all pages. Create +layout.svelte in src/routes folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<header>
	<nav class="nav">
		<div class="nav-center">
			<a href="/">Home</a>
			<a class="active" href="/blog">Blog</a>
			<a href="/about">About</a>
		</div>
	</nav>
</header>

<main class="container">
	<slot />
</main>

<footer>
	<div class="">(c) My blog</div>
</footer>

<style>
	main {
		margin-bottom: 50px;
	}
	footer {
		font-size: smaller;
		padding-top: 10px;
		position: fixed;
		height: 50px;
		background-color: gainsboro;
		bottom: 0px;
		left: 0px;
		right: 0px;
		margin-bottom: 0px;
	}
</style>

The blog looks much better.

sveltekit-blog-with-layout

Click on the navigation links to see some SPA action.

Take note of a couple of interesting things..

  • CSS is included in the layout itself, which is also a Svelte component. This CSS is scoped to the layout, but can manifest itself across the apps because of its nature (e.g., you see the layout across pages)
  • slot houses the components that gets rendered as pages. For e.g, the About page HTML replaces <slot>

Let us improve this layout a bit. Instead of typing in header in the same layout, let us all header info in a separate component.

Create a new file src/lib/Header.svelte and move header content from +layout.svelte.

1
2
3
4
5
6
7
8
9
<header>
	<nav class="nav">
		<div class="nav-center">
			<a href="/">Home</a>
			<a class="active" href="/blog">Blog</a>
			<a href="/about">About</a>
		</div>
	</nav>
</header>

Create a new file for styles at src/lib/styles/custom.css and move style content from +layout.svelte.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
main {
	margin-bottom: 50px;
}
footer {
	font-size: smaller;
	padding-top: 10px;
	position: fixed;
	height: 50px;
	background-color: gainsboro;
	bottom: 0px;
	left: 0px;
	right: 0px;
	margin-bottom: 0px;
}

The folder src/lib can contain the internal library of the app (which is a blog in our case). $lib is processed internally by Svelte to point to the right location of the components, styles or anything that you need to reuse across your app.

Import Header.svelte and custom.css in the the layout file src/routes/+layout.svelte.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<script>
	import Header from '$lib/components/Header.svelte';
	import '$lib/styles/custom.css';
</script>

<Header />

<main class="container">
	<slot />
</main>

<footer>
	<div class="">(c) My blog</div>
</footer>

Well, we are no accomplishing much by moving 5 lines of code, but the technique demonstrates using reusable components in Svelte. You can add global styles in layout or in app.html - we did the latter for the chota css library.

The end result will be the same as earlier, but also note that -

  • View the page source to see the CSS embedded in the HTML page
  • HTML + CSS is mixed and sent by server, not rendered on client

Let us change src/blog/+page.svelte in a similar way.
Create a new file src/lib/styles/blog.css -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ul {
	list-style: none;
	padding-left: 0;
}

.sidebar-content {
	margin-top: 4em;
	position: fixed;
	padding: 10px;
	width: 180px;
	border: 1px silver solid;
}


You can now change src/blog/+page.svelte to include the new blog-sidebar class.. Or, do something even better using our knowledge layouts.

Create a new file src/blog/+layout.svelte -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<script>
	import '$lib/styles/blog.css';
	let featLinks = [
		{ url: '/about', title: 'About' },
		{ url: '/blog/hello-world', title: 'Hello World' }
	];
</script>

<main>
	<div class="row">
		<div class="col-8">
			<slot />
		</div>
		<div class="col-4">
			<div class="sidebar-content">
				<p>
					<strong>Featured Links</strong>
				</p>

				<ul>
					{#each featLinks as featLink}
						<li>
							<a href={featLink.url}>
								{featLink.title}
							</a>
						</li>
					{/each}
				</ul>
			</div>
		</div>
	</div>
</main>

Note that -

  • This layout file is automatically included for all routes under blog. In other words - the layout is used for the blog home page and all blog posts.
  • The class row, column etc. are provided by chota css and makes the page is fully responsive.
    Also, you can modify fetching the featured content in real-time - we are not going there at the moment.

Lo and behold - your new blog layout. Resize the window to see the side bar move to the side or to the bottom.

blog-layout-sidebar

Support Markdown

We can continue to write HTML in each of the blog pages, or we could make things easier for writing content using markdown. We will do the latter, of course.

Handle markdown using a library called mdsvex, which will preprocess markdown files and convert them to render to web pages.

Install mdsvex to get started.

1
pnpm i -D mdsvex

Open svelte.config.js in the root folder. Change config -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const config = {
	kit: {
		adapter: adapter()
	},
	preprocess: [
		mdsvex({
			extensions: ['.md']
		})
	]
};

This config tells mdsvex to handle md files and convert them to HTML. It will also enable us to use Svelte components within markdown.

Now, there is more than one way to read Markdown files and serve content to the client. We will -

  1. Write server logic to read markdown files and convert the title/path to JSON, and retrieve list of files to show the list of blog posts
  2. Write even more server logic to read markdown file content for a single post and show content on the blog post page

Serve List of Blog Posts

Create a new file routes/api/posts/server.js in the root folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { json } from '@sveltejs/kit';

export const GET = async () => {
	console.log('GET invoked..');
	const allPosts = await fetchPosts();
	const sortedPosts = allPosts.sort((a, b) => {
		return new Date(b.meta.date) - new Date(a.meta.date);
	});
	console.log('Posts:', sortedPosts);
	console.log('.. GET done');

	return json(sortedPosts);
};

async function fetchPosts() {
	const postFiles = import.meta.glob('/src/routes/blog/*.md');
	const posts = Object.entries(postFiles);

	const allPosts = await Promise.all(
		posts.map(async ([path, resolver]) => {
			const { metadata } = await resolver();
			const postPath = path.slice(11, -3);

			return {
				meta: metadata,
				path: postPath
			};
		})
	);

	return allPosts;
}

In this file -

  1. We just read all files
  2. Retrieve metadata (markdown frontmatter) within the --- lines to check title and date
  3. Support GET request to serve the list of posts sorted by date

Sveltekit runs this logic only on the server. Navigate to http://localhost:5173/api/posts to see the JSON content

Change routes/blog/+page.svelte to -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<script>
	let posts;
	let promise = getPosts();

	async function getPosts() {
		console.log('blog loading..');
		const res = await fetch('http://localhost:5173/api/posts');
		posts = await res.json();
		console.log('posts', posts);
		console.log('.. blog loading done');
		return posts;
	}
</script>

<h1>Blog</h1>
<p>This is an awesome blog about sveltekit.</p>

{#await promise}
	<p>fetching posts..</p>
{:then posts}
	{#each posts as post}
		<div class="list-post"><a href={post?.path}>{post?.meta?.title}</a></div>
	{/each}
{/await}

  1. getPosts fetches post metadata from API
  2. Rest of the content including await/then is standard svelte to support promises (more details here)

You should now be able to see blog posts at http://localhost:5173/blog.

sveltekit-blog-list-markdown

Display a Single Blog Post

Create a new file routes/[blog]/[slug]/page.js] -

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export async function load({ params }) {
	const post = await import(`../${params.slug}.md`);
	const { title, date } = post.metadata;
	const content = post.default;

	return {
		content,
		title,
		date
	};
}

This will help us to -

  1. Get the parameter passed by the client (for e.g. when the user navigates to a particular blog post)
  2. Get the markdown file in the provided path
  3. Retrieve content, title and date, and return them

The corresponding routes/[blog]/[slug]/page.svelte] will display content-

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<script>
	/**
	 * @type {{ title: any; date: any; content?: any; }}
	 */
	export let data;
	const { title, date, content } = data;
</script>

<article>
	<h1>{data.title}</h1>
	<p class="subdued">Published: {data.date}</p>
	<svelte:component this={data.content} />
</article>

Create a few markdown files in routes/[blog]/ - their names are used in the URL (for e.g. I created latest.md with sample content).

Navigate to http://localhost:5173/blog/latest to see the blog post.

sveltekit-blogpost

That is it - play around to see all the magic that sveltekit can do for you.
You will find the complete code on Github.

Conclusion

Sveltekit provides a structured way to develop a full stack application. Coding in svelte feels intuitive for me, but Sveltekit may take some getting used to.

  1. The hundred files that we have to deal with is a confusing start in Sveltekit. Is moving away from something like getServerSideProps method on the same page to distinct set of pages an advantage? Maybe, but it’s sure weird
  2. Navigation b/w methods in the same file is simpler than navigating files for simple use cases. We could always separate out code to different files if a specific file is turning out to be too complex
  3. The naming conventions - +page.svelte, +page.js, +page.server.js, +server.js, +layout.svelte etc. may provide a structured approach to code while keeping us from making mistakes, but it sure is confusing. Decisions like “do I code this logic in page, page.server, server or even layout (?)” for a few use cases may be a turn off

Probably all of this comes down to developing the muscle memory to do things in a different way. That and enough people talking about and agreeing to conventions and best practices will certainly help.

For now the go to choice for the simple applications I code will continue to be Vue/Nuxt.

Stay in touch!
Share on

Prashanth Krishnamurthy
WRITTEN BY
Prashanth Krishnamurthy
Technologist | Creator of Things