How to Build a SvelteKit Blog with Hyvor Blogs Headless CMS

Build a sveltekit blog with headless cms. This tutorial covers setup, fetching content via APIs, and creating a unique design—all without authentication hurdles. Perfect for developers seeking flexibility and performance!

In this tutorial, I will walk you through building a blog using SvelteKit and Hyvor Blogs, a Headless CMS for blogging.

We are big fans of Svelte and use it for development. That is why we started with Svelte in our headless CMS tutorials.

Before starting the tutorial, here is a brief explanation of Headless CMS for beginners. If you aren’t a novice, skip to the tutorial. Note that this tutorial uses Svelte 5: hence this is a Svelte 5 blog.

What is a Headless CMS, and Why Use It?

Basically, a headless CMS stores your content and lets you use it anywhere you want. It's called "headless" because it doesn't have a fixed "head" (the front-end part that visitors see) – you can attach any kind of "head" you want! In other words, a headless CMS can send your content anywhere through its API. All you need is to fetch data from the API and present your content to your users as you prefer.

Using a headless CMS, content creators can still use a friendly interface to write and manage content, while developers can build amazing experiences using whatever tools they love. It's perfect for companies or enterprises that want their content to appear in lots of different places without having to copy-paste everything manually. Not only companies, but developers who love hassle-free content creation while building beautiful frontends can try this too.

Here is a simple image infographic to help you understand how a headless CMS differs from a traditional CMS.

Traditional CMS vs Headless CMS - Build svelte blog with Hyvor Blogs Headless CMS
Traditional CMS vs Headless CMS

Why Hyvor Blogs as your Headless CMS?

Hyvor Blogs is an all-in-one blogging platform that mainly focuses on blogging: no e-commerce, no portfolios, just blogging. As such, we offer bloggers the following features.

  • Multi-language blogging including RTL language blogging.

  • User-friendly, team-friendly console with markdown-friendly rich text editor

  • Data API for headless cms

  • Hosting privileges: subdomain, custom domain, sub-directory, or anywhere you like.

  • Developer-friendly: console API, delivery API, webhooks, etc.

  • Fast blogging and so much more.

Now let’s move to the tutorial!

Step 1: Set Up Your SvelteKit Project

  1. Set up a new SvelteKit project:
    Begin by creating a new SvelteKit app. Run the following command in your terminal:

    1npx sv create my-app

    Replace my-app with your preferred project name.

    As you hit “Enter” you will get several questions by svelte CLI for your preferences, the following on the below image were mine. Make sure to change things according to your liking.

    create-svelte-blog-with-headless-cms
  2. Navigate to the project directory:
    Move into the newly created project folder:

    1cd my-app

  3. Install project dependencies:
    Once inside the project folder, install the necessary dependencies:

    1npm install

  4. Run the development server:
    Start the development server to preview your project:

    1npm run dev -- --open

    This will start a local server, usually available at http://localhost:5173 . This command will open your project from the browser. Then you will see something like this.

sveltekit blog with Hyvor Blogs headless CMS
Sveltekit Blog at Initial Phase

This is the file hierarchy of our project for now.

1my-app/
2├── src/
3 ├── lib/
4 ├── routes/
5 ├── +layout.svelte
6 ├── +page.ts
7 ├── +page.svelte
8 └── app.css
9├── static/
10├── tests/
11├── .svelte-kit/
12├── node_modules/
13├── package.json
14├── svelte.config.js
15├── tsconfig.json
16├── vite.config.js
17└── README.md

Step 2: Create your blog at Hyvor Blogs

Go to the Hyvor Blogs console and create your blog. Once you have created your blog in Hyvor Blogs, we need t the subdomain of the blog.

Go to Console -> Settings -> Hosting -> Subdomain.

build a sveltekit blog with headless cms Hyvor Blogs - Hyvor Blogs console
Get your blog’s subdomain

For this tutorial, I’m using headless-cms as my subdomain.

To work on building this Svelte blog with Hyvor Blogs as a headless CMS, we use Hyvor Blogs Data API. Here is the Data API documentation of Hyvor Blogs. Here are some things you need to know before start using HB data API.

  • All the responses are in the JSON format

  • All the endpoints use the HTTP GET method

  • The base path https://blogs.hyvor.com/api/data/v0/{subdomain}

    For example in this case, https://blogs.hyvor.com/api/data/v0/headless-cms

Let’s continue.

Step 3: Index Page

In this step, we are going to create a homepage for the blog: the page where we list all our blog posts.

  1. Create a /src/routes/+page.ts to fetch data from Hyvor Blogs Data API. Using the base path, call Data API’s posts object to fetch post data.

    1export async function load({ fetch }) {
    2 const response = await fetch('https://blogs.hyvor.com/api/data/v0/headless-cms/posts');
    3
    4 const { data } = await response.json();
    5 return { posts: data ?? [] };
    6}

    Rather than doing this in +page.svelte itself, we used +page.ts because using +page.ts in SvelteKit enables server-side rendering (SSR) for fetching data on the server for faster initial data loading. It also preloads data too.

  2. Goto /src/routes/+page.svelte file which renders the post loop data you called from the Data API. This will load the posts loop of your blog.

    1<script lang="ts">
    2 import type { PageData } from './$types';
    3 let { data }: { data: PageData } = $props();
    4 const { posts } = data;
    5</script>
    6
    7<div class="posts">
    8 {#each posts as post}
    9 <article>
    10 <h2><a href={`/${post.slug}`}>{post.title}</a></h2>
    11 <p>{post.description}</p>
    12 </article>
    13 {/each}
    14</div>

    Once completed, it will look something like this. I have added a header and footer and some simple CSS for better visualization.

    build a sveltekit blog with headless cms - Post loop in index/home page
    Index/homepage with list of posts

    There are so many endpoints given by Hyvor Blogs Data API for you to display data on your blog. See more.

Step 4: Post Page

In this step, we are going to show the content of posts using the slug of each post. Thus, a particular post loads with the URL headless-cms/post-slug .

  1. Set Up Routing: Create a new route for displaying posts. Create a folder [slug] in the routes folder: /routes/[slug] .

  2. Then create +page.ts and +page.svelte in it.

  3. Fetch post data of each post using +page.ts.

    1import type { PageLoad } from './$types';
    2
    3export const load: PageLoad = async ({ fetch, params }) => {
    4 const slug = params.slug;
    5 const apiUrl = `https://blogs.hyvor.com/api/data/v0/headless-cms/post?slug=${slug}`;
    6
    7 try {
    8 const response = await fetch(apiUrl);
    9
    10 if (!response.ok) {
    11 throw new Error(`Post not found: ${slug} ${response.status} ${response.statusText}`);
    12 }
    13
    14 const post = await response.json();
    15
    16 return { post };
    17 } catch (error) {
    18 console.error('Error fetching post:', error);
    19 return { post: null };
    20 }
    21};
    22

  4. Render the fetched post data in +page.svelte .

    1<script lang="ts">
    2 import type { PageData } from './$types';
    3 let { data }: { data: PageData } = $props();
    4</script>
    5
    6<article>
    7 <h1>{data.post.title}</h1>
    8 <p>{data.post.description}</p>
    9 <div class="content">
    10 {@html data.post.content}
    11 </div>
    12</article>

    That’s all for showing post content. Here is a snap of our example blog.

build a sveltekit blog with headless cms - post content without css
Post content without CSS

Step 5: Pagination

To set up the pagination of your blog, we use the Pagination Object of Hyvor Blogs data API. To limit the number of posts we show on each page, we will be using limit and page params.

We are going to create pagination pages on routes like /page/1

  1. Go to /routes/+page.ts . We have to make some changes to our previous code to integrate pagination.

1export async function load({ fetch }: { fetch: any; params: Record<string, string> }) {
2 const limit = 2; // Number of posts per page
3 const response = await fetch(
4 `https://blogs.hyvor.com/api/data/v0/headless-cms/posts?limit=${limit}&page=1`
5 );
6
7 const { data, pagination } = await response.json();
8
9 return {
10 posts: data,
11 pagination: pagination
12 };
13}

As you can see, I have used limit param to limit the posts per page and page param. And we are getting data from the pagination object.

  1. Go to /routes/+page.svelte . Let’s add pagination buttons for navigation. You can simply create pagination buttons as follows in +page.svelte.

1//............. +page.svelte
2<div class="pagination">
3 {#if data.pagination.page > 1}
4 <a href="/page/{data.pagination.page - 1}">Previous</a>
5 {/if}
6 {#if data.pagination.page < data.pagination.pages}
7 <a href="/page/{data.pagination.page + 1}">Next</a>
8 {/if}
9</div>

But, to avoid repetition and as a best practice: abstraction, let’s create a component for Pagination.

  1. Go to /lib/components and create a file Pagination.svelte for our pagination component.

1<script lang="ts">
2 import type { PageData } from '../../routes/$types';
3 let { data }: { data: PageData } = $props();
4</script>
5
6<div class="pagination">
7 {#if data.pagination.page > 1}
8 <a href="/page/{data.pagination.page - 1}">Previous</a>
9 {/if}
10 {#if data.pagination.page < data.pagination.pages}
11 <a href="/page/{data.pagination.page + 1}">Next</a>
12 {/if}
13</div>
14
15<style>
16 .pagination {
17 display: flex;
18 justify-content: center;
19 gap: 1rem;
20 margin-top: 2rem;
21 }
22</style>

Now you can use this component anywhere you want.

  1. Go to /routes/+page.svelte and add our pagination component as follows.

1<script lang="ts">
2 import Pagination from '$lib/components/Pagination.svelte';
3 import type { PageData } from './$types';
4 let { data }: { data: PageData } = $props();
5</script>
6
7<div class="posts">
8 {#each data.posts as post}
9 <article>
10 <h2><a href={`/${post.slug}`}>{post.title}</a></h2>
11 <p>{post.description}</p>
12 </article>
13 {/each}
14</div>
15
16<Pagination {data} />

Now, we are going to create dynamic routes for pagination pages. SvelteKit's dynamic routing will allow us to create a route that can handle /page/{n} URLs. We need to add a [page]folder inside /routes/ to handle the pagination logic.

  1. Create your folder structure as follows and create the files as shown below.

1/src/routes/page/[page]/
2 ├── +page.svelte
3 └── +page.ts
  1. Go to /routes/page/[page]/+page.ts . This file is similar to /routes/+page.ts, but it handles pagination for /page/{n} URLs.

1export async function load({ fetch, params }) {
2 const page = parseInt(params.page ?? '1'); // Default to page 1 if no page param
3 const limit = 2; // Number of posts per page
4 const response = await fetch(
5 `https://blogs.hyvor.com/api/data/v0/headless-cms/posts?limit=${limit}&page=${page}`
6 );
7
8 const { data, pagination } = await response.json();
9
10 return {
11 posts: data,
12 pagination: pagination
13 };
14}
  1. Go to /routes/page/[page]/+page.svelte This is the same as the code above we added in /routes/+page.svelte, but it will handle /page/{n} dynamically.

1<script lang="ts">
2 import Pagination from '$lib/components/Pagination.svelte';
3 import type { PageData } from './$types';
4 let { data }: { data: PageData } = $props();
5</script>
6
7<div class="posts">
8 {#each data.posts as post}
9 <article>
10 <h2><a href={`/${post.slug}`}>{post.title}</a></h2>
11 <p>{post.description}</p>
12 </article>
13 {/each}
14</div>
15
16<Pagination {data} />

Et voila! We have completed adding pagination to our Svelte blog.

build a sveltekit blog with headless cms - Pagination
Pagination

Step 6: Build your Blog

Building your blog can be done in 2 ways. Each approach has its pros and is suited according to your needs.

1. Static Blog

In a static blog, all content is pre-generated at its build time. Pages are static HTML files, making this method fast, secure, and easy to deploy. This is ideal for blogs with rarely changing content, limited user interaction, and a focus on performance.

If you want to build this blog as a static blog, we have to install the Sveltekit static adapter. Run the following command in a different terminal.

1npm i -D @sveltejs/adapter-static

This installs the static adapter as a development dependency. This static adapter enables you to generate a fully static version of your site for deployment to platforms like Netlify, Vercel, GitHub Pages, or any static file server.

Then, update svelte.config.js: After installation, configure the adapter in your svelte.config.js file:

1import adapter from '@sveltejs/adapter-static';
2
3export default {
4 kit: {
5 adapter: adapter({
6 // default options
7 pages: 'build',
8 assets: 'build',
9 fallback: null,
10 precompress: false
11 })
12 }
13};

After that, we are going to add the prerender option to our root layout. To do that, go to routes folder and create a +layout.ts file

1export const prerender = true;

Now we can build our static blog. Run the build command to generate the static site:

1npm run build

Now, you can use the preview command to serve the static files locally:

1npm run preview

To deploy, upload the build/ folder to your chosen static hosting provider.

2. Dynamic Blog

A dynamic blog fetches and renders content at runtime. Using server-side rendering (SSR) or client-side rendering (CSR), it can serve up-to-date content by fetching data from APIs or databases. So this is an ideal option if your blog is with frequent updates.

If you choose to have a dynamic Svelte blog, here is how you can build and deploy your blog.

We need an adapter that supports server-side rendering (SSR). So make sure to choose the one that you want. For this tutorial, I'm using Node.js. To install it,

1npm i -D @sveltejs/adapter-node

Then you have to update your configuration file which is svelte.config.js to use the server-side adapter.

1import adapter from '@sveltejs/adapter-node';
2
3export default {
4 kit: {
5 adapter: adapter()
6 }
7};

After all that, you can simply run the following command to build your blog. This will create the production server in the output directory specified in the adapter options, defaulting to build.

1npm run build

To preview your blog, run the following command.

1npm run preview

That’s it. Now you can deploy your site to a runtime environment as you prefer.

Tips

Yay! You've built a SvelteKit blog using a Headless CMS Hyvor Blogs.

Feel free to expand this project using our Data API endpoints and objects.

Comments

Published with Hyvor Blogs