Creating a Personal Blog Website with Qwik

In this blog post, I'll provide an overview of how to build a website to host a personal blog using the Qwik frontend framework. Qwik is designed for instant-loading and high performance, making it an excellent choice for building modern web applications and an interesting framework to explore.

Table of Contents

  1. Introduction
  2. Setting Up the Environment
  3. Creating a New Qwik Project
  4. Building the Blog Layout
  5. Adding Blog Posts
  6. Conclusion

Introduction

Qwik is a cutting-edge framework from the creator of Angular that aims to provide better start up performance than any other frontend framework today. In this guide, I will walk you through the steps to create a personal website and blog from scratch using Qwik.

On their official website, the Qwik team provide a great high-level overview of how Qwik differs from other frontend frameworks:

General-purpose: Qwik can be used to build any type of web site or application.

Instant-on: Unlike other frameworks, Qwik is resumable which means Qwik applications require 0 hydration. This allows Qwik apps to have instant-on interactivity, regardless of size or complexity.

Optimized for speed: Qwik has unprecedented performance, offering sub-second full page loads even on mobile devices. Qwik achieves this by delivering pure HTML, and incrementally loading JS only as-needed.

Additionally, Qwik uses JSX for templating, which makes it easy to get started for developers familiar with React.

Does all this make it the perfect choice for all new projects? Probably not. Does it make it perfect for a blog? Not necessarily. But does it make it an interesting choice to explore and learn about? Absolutely! So let's get started.

Setting Up the Environment

First, make sure you have Node installed on your machine. You can download and install it from the official Node.js website, or you can use NVM, the Node Version Manager. You can learn more about NVM on their GitHub page.

I will be using pnpm as my package manager, but you can use npm or yarn if you prefer. Just swap out pnpm for npm or yarn in the commands below.

Creating a New Qwik Project

Once you have your package manager ready, create a new Qwik project. Open your terminal and run:

pnpm create qwik@latest

You will be prompted to choose a directory to install the files in and a template for the project. We'll put the project in ./my-blog and use the "Empty App" template. Select "Yes" to install pnpm dependencies and to initialize a new git repository.

Navigate to the directory and open it in your code editor:

cd my-blog

if using VS Code:

code .

Building the Blog Layout

Let's set up the basic layout of our blog.

Creating the Header Component

In the src/components directory, create a new file called index.tsx under a new folder called header.

This structure allows us to group everything related to the component in one place, such as styles, tests, and sub-components you can use to break up complexities in the primary exported component. A minor additional benefit is it will allow us to shorten the imports we use for it. For example components/header instead of components/header/header.

You can forego the additional file structure and simply keep a file called header.tsx at the root of components if you prefer. Feel free to organize your files in any way that makes sense to you.

Add the following code to the new file. Add styling and other details as you like. I will not be covering styling in this guide, but Qwik supports just about any strategy for styling you could want, including standard CSS, CSS Modules, CSS-in-JS, SASS, Tailwind, and more.

import { component$ } from "@builder.io/qwik"
import { Link } from "@builder.io/qwik-city"

export const Header = component$(() => {
  return (
    <header>
      <Link href="/">
        <strong>My Blog</strong>
      </Link>

      <nav>
        <ul>
          <li>
            <Link href="/posts/">Posts</Link>
          </li>
          <li>
            <Link href="#">About</Link>
          </li>
          ... More links here ...
        </ul>
      </nav>
    </header>
  )
})

In this component, we are creating a simple header and nav bar that will be present on all pages. We are using the component$ function from Qwik to define it. You will see the dollar sign a lot in Qwik. You can think of it as indicating a lazy loading boundary, which allows Qwik to load the component only when it is needed. You can read more about defining components in the Qwik documentation.

We also use the Link component to create links to different pages in the blog. The Link component handles client-side, single page navigation. Interestingly, a Qwik app can be either a single page application or a multi-page application depending on how you structure your routes. Instead of Link, we could use the standard HTML a tag to trigger full page navigation. You can even have both in the same app, depending on your needs. Read more about Qwik routing here.

Adding the Header to the Main Layout

Edit the src/routes/layout.tsx file to include the Header component:

import { component$, Slot } from "@builder.io/qwik"
import type { RequestHandler } from "@builder.io/qwik-city"
import { Header } from "~/components/header"

...
// Default caching code at base route
...

export default component$(() => {
  return (
    <div>
      <Header />
      <main>
        <Slot />
      </main>
    </div>
  )
})

Qwik uses a file based routing system similar to other frameworks, such as Next.js. This layout.tsx file is the main layout component that will be used for all pages in the application. Layouts must be named layout and export a default component.

Pages will be rendered inside the Slot component, which acts as a placeholder for the content of each page, similar to children in React. You can read more about slots in the Qwik documentation.

Adding a home page

Update the index.tsx in the src/routes directory. Copy and paste the following code to the file, or add something more interesting!

import { component$ } from "@builder.io/qwik"
import { Link, type DocumentHead } from "@builder.io/qwik-city"

export default component$(() => {
  return (
    <>
      <h1>Welcome to my Blog!</h1>
      <p>
        There isn't much here yet, so why not check out my{" "}
        <Link href="/posts/">posts</Link>?
      </p>
    </>
  )
})

export const head: DocumentHead = {
  title: "My Blog",
  meta: [
    {
      name: "description",
      content: "A blog for all things me",
    },
  ],
}

The only new thing here is the DocumentHead object. This is used to define the metadata and other tags for the HTML head section of the document. The title property is used to set the title of the page, and the meta array is used to define additional metadata. Anything you can add to standard HTML head sections can be added here.

Adding Blog Posts

Layouts will not be rendered on their own, but are instead used as a wrapper around the content of each page. In order to render a page, Qwik requires an index.(tsx,jsx,mdx) at the base of each path. So let's get something on the screen by creating a blog post!

There are many possible ways to structure a blog in Qwik, but for simplicity, we will create our posts using MDX, which is a Markdown language that allows you to import and use JSX components. Qwik supports MDX out of the box, and compiles it to Qwik components. This makes it a great way to write content heavy web pages like blog posts. It may not be ideal for non-technical writers who are not familiar with Markdown, or for blog sites that have authors as users, like Medium, which require more scalable solutions. But it works great for personal blogs. Especially if you are a developer who is comfortable with Markdown and working in frontend frameworks.

Since Qwik uses a file based router, the directory structure will determine the URL of the blog post. For example, a post, index.mdx, in src/routes/posts/my-blog-post/ will be accessible in the website at /posts/my-blog-post/.

We will do something similar to this, but with a slight twist. We will use the following structure under the routes directory (including the layout and index files we created earlier):

| routes
| -- posts
|  | -- (posts)
|  |  | -- my-blog-post
|  |  |  | -- index.mdx  // posts/my-blog-post page
|  |  | -- layout.mdx    // common post layout
|  |  | -- posts.ts      // helper file for posts
|  | -- index.tsx        // posts page
| -- index.tsx           // home page
| -- layout.tsx          // global layout

This looks mostly like what we discussed earlier, with the exception of the (posts) directory. In another similarity to Next.js, the parentheses indicate that this part of the path is ignored by the router. For example posts/(posts)/my-blog-post/index.mdx becomes posts/my-blog-post/ in the browser. This is a way to group related files together without creating a new route. In this case, we are adding the extra folder in order to create a new layout.tsx file that will help structure the content of each individual post page below it.

Qwik also supports dynamic routes. Since we are writing our posts directly in markdown, we are defining the path explicitly instead. But if you were using a database to store and retrieve posts, you could use square brackets in the folder name to define a route variable. For example, src/routes/posts/[title]/index.tsx would match the path /posts/:title/, where :title is any string representing a blog post title. You could then use the title variable in your component to fetch the post content and display it in your page.

Go ahead and create the necessary files and directories in your project.

Creating a New Blog Post

Let's create a new blog post using MDX.

If you haven't already, create a file with the path src/routes/posts/(posts)/my-blog-post/index.mdx. Add anything you like to this file, but here is a small example:

---
title: My First Blog Post
description: A totally rad blog post!
author: Evan Douglass
og:
  - title: true
    description: true
  - image: /images/my-image.jpg
    image:alt: A picture of me
---

Welcome to my blog!

The content wrapped in dashes at the top of the file is called frontmatter. It is used to define metadata about the post, including title, description, and author. The frontmatter section can contain any arbitrary data, but anything that is named the same as "well known" HTML meta tags will be added to the document's head section as that tag, such as all the lines here. This is optional, but it is good practice to include it for SEO (Search Engine Optimization) and for sharing on social media. Read more about setting HTML head tags in the Qwik documentation.

You can also use this information in other Qwik files. We will see this shortly. Notice we do not provide a main heading in the body of the markdown. This is intentional, because we will use the information in the frontmatter to display the title and date of the post separately. This will allow us to more easily provide custom styles for the header of the article.

Create a layout for the post - and all future posts - at src/routes/posts/(posts)/layout.tsx:

import { Slot, component$ } from "@builder.io/qwik"
import { useDocumentHead } from "@builder.io/qwik-city"
import { formatDate } from "~/utils/formatDate"

export default component$(() => {
  // Get metadata and frontmatter
  const head = useDocumentHead()

  return (
    <article>
      <header>
        <h1>{head.title}</h1>
        <time dateTime={head.frontmatter.date}>
          <span>{formatDate(head.frontmatter.date)}</span>
        </time>
      </header>
      <section>
        <Slot />
      </section>
    </article>
  )
})

Style this in any way you like. You can view the top of this very blog post as an example!

formatDate is a simple helper function to create a formatted date string. It is defined by the code below, but feel free to adjust it to your tastes. You can add this to a new file in a utils directory, or in the same file as the component.

export function formatDate(dateString: string) {
  return new Date(`${dateString}T00:00:00Z`).toLocaleDateString("en-US", {
    day: "numeric",
    month: "long",
    year: "numeric",
    timeZone: "UTC",
  })
}

You should now be able to view the post at http://localhost:5174/posts/my-blog-post after running pnpm start.

There is still something missing though. Users don't have a way to browse posts!

Adding a List of Blog Posts

In order to allow users to view blog posts, they must be able to browse through them. There may be better ways to do this. But for the sake of simplicity, we will keep a list of all the posts in our code and cycle through them to render a list of previous posts that users can look through.

In src/routes/posts/(posts)/posts.ts add the code below. There are a lot of posts in this path, which may start to get confusing. As a quick reminder, the first is the router path, the second is used for grouping files without adding to the router path, and the last is storing references to your posts.

export interface Post {
  slug: string
  title: string
  description: string
  date: string
}

export const POSTS: Readonly<Post[]> = [
  {
    slug: "creating-a-blog-with-qwik",
    title: "Creating a Blog Website with Qwik",
    description:
      "Learn how to create a blog website using the Qwik frontend framework.",
    date: "2024-05-20",
  },
]

This is a simple array of objects that represent data for each post. You can add as many posts as you like, and you can add more properties to each post depending on what you need. Add new posts to the top of the array to keep the list in reverse chronological order, as you would view it in the browser.

Next, add the following code to src/routes/posts/(posts)/index.tsx to display the list of posts.

import { component$ } from "@builder.io/qwik"
import { Link } from "@builder.io/qwik-city"
import { formatDate } from "~/utils/formatDate"
import { POSTS } from "./posts"

export default component$(() => {
  return (
    <>
      <h1>Posts</h1>
      <ul>
        {POSTS.map((post) => (
          <li key={post.slug}>
            <time dateTime={post.date}>{formatDate(post.date)}</time>
            <Link href={`/posts/${post.slug}`}>
              <h2>{post.title}</h2>
            </Link>
            <p>{post.description}</p>
          </li>
        ))}
      </ul>
    </>
  )
})

And with that we have a working blog!

Conclusion

There is a lot more work needed on this starter code to get ready for deployment. But with the basic building blocks in place, you can start adding elements and experimenting with Qwik. As a learning exercise, I encourage you to build on this code and see what you can create. Or take what you have learned to build out an entirely new project. The Qwik documentation is a great resource to help you along the way.

Some important things we did not cover in this post are state management and component life-cycles. These are some of the more interesting parts of Qwik, and will be important to understand to evaluate if Qwik is right for your next project.

The developer ergonomics of using these features are similar to React, so should make them easy to pick up if you already know React. But they do behave slightly differently. Be sure to read the documentation to understand how they work. I'll cover these topics more in future posts as well.

Deploying Your Blog

When you are ready to deploy, Qwik has several ready-made integrations with popular hosting platforms, including Cloudflare (my personal choice for this website), AWS, Google Cloud, Azure, Vercel, Netlify, and more. You can read more about deploying Qwik applications here.