# Your Blog, Your Way: SvelteKit and Headless Hashnode Make It Possible

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">How to build a simple, completely customised blog from scratch using Headless Hashnode and SvelteKit.</div>
</div>

## Why *headless*?

As developers, we can't help but build and customise. If you're anything like me it's easier to build things instead of writing my blog posts 😅 now we can do both!

I decided to use Hashnode because I wanted as few barriers to entry as possible to get blogging, whilst using my own domain. Now that Hashnode have released *headless* (an API that allows you to query your posts, comments and profile etc.) I can start writing my posts safely in the knowledge that I can take my time creating a perfectly customised site without compromising on getting the posts written.

Once I'm ready I simply flip the switch...

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698657323250/056fad55-19cf-4c2c-9ed1-78ca9e2633ad.png align="center")

Hashnode has provided a [starter-kit](https://github.com/hashnode/starter-kit) for NextJS and Vercel but if like me, you are fully on the Svelte hype train (or just want to see how easy it is to build without the template) I've got your fix here.

We will be creating our site using:

* SvelteKit
    
* Tailwind CSS
    
* Houdini (for the GraphQL connection)
    

## Using the Hashnode API

### First GraphQL Query

The Hashnode API utilises [GraphQL](https://graphql.org/) - if you've not used it before, it's an alternative to REST where you explicitly ask for the data you need in the form of a 'query' rather than targeting a particular endpoint.

In our case, instead of querying, as an example, `api.hashnode.com/your-blog-id/posts` and getting a large (and perhaps slow) response instead you would send a request to `gql.hashnode.com` with the below query that only returns what we've specified.

<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">You will need to change the host to your blog domain</div>
</div>

```graphql
query PostPreview {
	publication(host: "engineering.hashnode.com") {
		title
		posts(first: 10) {
			edges {
				node {
					title
					brief
					slug
					url
					coverImage {
						url
					}
				}
			}
		}
	}
}
```

### API Playground

Hashnode offers a great [API playground](https://gql.hashnode.com/) for experimenting with queries to ensure they are well-formed and return the data we expect.

We can see our query is correct and is returning all the data we need to create a list of preview cards that link to the individual blog pages.

![Screenshot of GQL playground](https://cdn.hashnode.com/res/hashnode/image/upload/v1698602360182/0dc65982-3317-48e4-b864-4894e3ff230c.png align="center")

## Project Set Up

Create your project and follow the [Tailwind docs](https://tailwindcss.com/docs/guides/sveltekit) to bootstrap your project

```bash
npm create svelte@latest sveltekit-hashnode-blog
```

or follow along in the repo:

[bentilling/sveltekit-hashnode-blog-template](https://github.com/bentilling/sveltekit-hashnode-blog-template)

### Setting up Houdini

Setting up the boilerplate code to get a GraphQL client set up in your project can sometimes be a bit of a headache, but with SvelteKit we can utilise the great library [Houdini](https://houdinigraphql.com).

This perfectly integrates into the existing file structure to get easy boilerplate-free access to our response data as we will see shortly.

Simply install the package

```bash
npm install --save-dev houdini
npx houdini@latest init
```

and enter `https://gql.hashnode.com` when prompted for the URL, and Houdini takes care of all the setup.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698603899769/c6d53164-2b51-42a5-be99-4e4d8129055a.png align="center")

If you run this you can see Houdini has (amongst other things):

* Added a houdini.config.js file which contains the GQL endpoint
    
* Updated `vite.config.ts` to ensure Houdini runs in the build process
    
* Added a `$houdini` folder that contains the types for a great local development experience
    

## Creating Post Overview Page

Let's use Houdini to create a post overview page.

1. Create a new file named `+page.gql` inside `src/route` and paste the PostPreview GQL query.
    
2. Create the `+page.svelte` file in the same directory.
    

```svelte
// src/routes/+page.svelte
<script lang="ts">
	import type { PageData } from '$houdini/types/src/routes/$houdini';

	export let data: PageData;

	$: ({ PostPreview } = data);
</script>

{#if $PostPreview?.data?.publication?.posts}
	<ul>
		{#each $PostPreview.data.publication.posts.edges as { node: { title, slug, brief, coverImage } }}
			<a href={`/posts/${slug}`}>
				<div class="p-6 bg-white rounded shadow-sm my-4">
					<h2 class="text-xl pb-5 font-semibold">{title}</h2>
					<div class="flex">
						<img class="rounded-lg shadow-lg w-1/2" src={coverImage?.url} alt={title} />
						<p class="m-4 w-1/2">{brief}</p>
					</div>
				</div>
			</a>
		{/each}
	</ul>
{/if}
```

There is some magic happening here so let's break it down.

When this page is navigated to, queries in the `+page.gql` file are executed - in our case, the PostPreview query.

The results of this query are passed via the PageData prop - identical to how you would receive data from `+page.ts` and `+page.server.ts` load functions.

This data is provided in a [Svelte store](https://svelte.dev/docs/svelte-store) so that we can react to changes. This is important because unlike in REST where we wait for a response and get a success or failure, GraphQL queries respond with a loading state to indicate if the data is currently being fetched - when this is happening we will have empty data and will want our UI to respond appropriately.

We then destructure the data from inside the store and use it as normal to build our preview components.

Adding a simple universal layout with a nav bar and blog title...

```svelte
// src/routes/layout.svelte
<script>
	import '../app.css';
</script>

<div class="flex flex-col bg-gray-200 w-screen">
	<nav class="bg-gray-600 text-white flex justify-between items-center">
		<h1 class=" font-mono text-xl p-6">Awesome blog built with Svelte and Hashnode Headless</h1>
	</nav>
	<div class="mx-auto grow max-w-3xl">
		<slot />
	</div>
</div>ss
```

and we have a basic blog home page using data from Hashnode!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698673647638/e15ef738-e99d-4c8a-b0de-96e4b853fc06.png align="center")

## Creating the post pages

Now that we've got our post previews working, let's create the individual blog pages.

First, add a new directory post/\[slug\] with two files `+page.svelte` and `+page.gql`, your directory should now look like this:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698666169338/68c19932-3abe-45cf-b0c5-21738ae00b9a.png align="left")

Then, update `+page.gql` with the query for our post data. You will notice the addition of `$slug`.

```graphql
query Post($slug: String!) {
	publication(host: "engineering.hashnode.com") {
		post(slug: $slug) {
			title
			content {
				html
			}
		}
	}
}
```

Here `$slug` is a variable that the query is expecting and is used to filter to the specific post ID.

This is automatically provided by Houdini for any matching parameters in the route path. In other words, if a user navigates to `your-blog.com/posts/great-article` SvelteKit knows that `great-article` is the `[slug]` and passes it as a variable to the query.

As before, let's use the data from the query to create a simple page:

```svelte
// src/routes/posts/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$houdini';

	export let data: PageData;

	$: ({ Post } = data);
</script>

<article class="bg-white px-6 max-w-screen-lg">
	{#if $Post.data?.publication?.post}
		<h1 class="text-4xl font-bold pt-10">{$Post.data.publication?.post?.title}</h1>
		{@html $Post.data.publication?.post?.content.html}
	{/if}
</article>
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">We are using <code>@html</code> tag here to directly render the HTML, make sure you trust the source of this data!</div>
</div>

Uh oh, you might notice an issue, because we're directly rendering the HTML we have no default styling and it looks a mess...😟

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698673779434/4ddc1e83-fcfc-4203-a521-7bd0ab6a7437.png align="center")

Tailwind comes to the rescue again...

### Tailwind CSS Typography Plugin

> Beautiful typographic defaults for HTML you don't control.

Tailwind CSS have a [library](https://tailwindcss.com/docs/typography-plugin) for exactly this purpose. First, add it to our dependencies.

```bash
npm install -D @tailwindcss/typography
```

Now we simply add the `prose` class and our text is magically formatted, we can further customise by using the `prose-[tag]:[attribute]` e.g. below we have underlined the h2 tags.

```svelte
...
		<div class="prose prose-h2:underline">
			{@html $Post.data.publication?.post?.content.html}
		</div>
...
```

Much better!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698673839506/9b1e15e0-7e1c-4455-91dc-ffeb08437cbb.png align="center")

And that's it! 🎉 Now simply deploy your code using your favourite platform and you have the beginnings of your fully customised blog powered by Hashnode!
