Create a new theme from scratch

In this recipe, we will create a new theme from scratch. We will (re)create the theme called "Minimalist" that is included in the Bloggrify core repository.

Getting Started

First, you need to create a new Nuxt application. You can do this by running the following command:

npx nuxi@latest init neo-minimalist

Then, you need to install the dependencies:

npm install @bloggrify/core
npm install -D sass-embedded

Then you have to explicitly say to Nuxt that you are using Bloggrify as an extended module. You can do this by adding the following line in your nuxt.config.js file:

    extends: [
        '@bloggrify/core',
    ],

Create a basic configuration file

Create a new file called app.config.ts in the root of your project. This file will contain the configuration of your theme.
This file will override the default configuration of Bloggrify. If you don't specify a configuration, the default configuration will be used.

Here is an example of a basic configuration file:

export default defineAppConfig({
    url: 'https://neo-minimalist.bloggrify.com/',

    theme: 'neo-minimalist',

    name: 'Neo-minimalist Demo',
    description:
        'A minimalist theme for Bloggrify',

})

That's probably not enough for a complete theme, but it's a good start. You can add more configuration options later. Don't forget to read the official documentation to know more about the configuration options.

Create the layout for the home page

Create a new folder called layouts/themes/neo-minimalist in the root of your project. This folder will contain the layouts of your theme.

Create a new file called home.vue in the layouts/themes/neo-minimalist folder. This file will be the layout for the home page.

Here is an example of a basic layout file:

<template>
    <div>
        <header>
            <h1>
                {{ title }}
            </h1>
        </header>

        <main>
            <div>
                <div >
                      {{ description }}
                </div>

              <div >
                <NuxtLink to="/archives">
                  Read all posts
                </NuxtLink>
              </div>
            </div>
        </main>
    </div>
</template>
<script setup lang="ts">

defineProps<{
    doc: unknown;
}>()
const config = useAppConfig()
const title = config.name
const description = config.description
</script>

Let's explain the code above:

  • The <template> section contains the HTML structure of the layout. In this example, we have a navigation bar and a main content area.
  • The <script setup> section contains the logic of the layout. In this example, we get the configuration from the app.config.ts file and display the title of the blog.

Create the index page

Create a new folder called content in the root of your project. This folder will contain the articles of your blog and the index page.

Create a new file called index.md in the pages folder. This file will be the index page of your blog.

---
id: "1"
title: "Home page"
description: "Basic home page"
date: "2024-12-31"
categories:
  - home
tags:
  - lorem
  - ipsum

layout: home
listed: false
---

Run the development server

You can now start the development server by running the following command:

npm run dev

Go to http://localhost:3000 in your browser, and you should see the home page of your blog.

This is cool, but, you don't have the list of all the articles. Let's add a new layout for the archives page.

Create the layout for the archives page

Create a new file called archive.vue in the layouts/themes/neo-minimalist folder. This file will be the default layout for the archive page.

Here is an example of a basic layout file:

<template>
    <div>
        <header>
            <h1>
                {{ title }}
            </h1>
        </header>

        <main>
            <div>
                <div >
                      All Posts
                </div>

              <div >
                <section class="mt-10">
                  <MinimalistListing title="All Posts"/>
                </section>
              </div>
            </div>
        </main>
    </div>
</template>
<script setup lang="ts">
const config = useAppConfig()
const title = config.name
</script>

This layout will display the list of all the articles. To do so, we use the MinimalistListing component. This component is provided by the @bloggrify/core package. But we will see how to create a custom component later.

Now, you can go to http://localhost:3000/archives in your browser, and you should see the list of all the articles.

The list is probably empty, because you don't have any articles yet. Let's add some content.

Create the default layout for the pages

Create a new file called default.vue in the layouts/themes/neo-minimalist folder. This file will be the default layout for all pages.

Here is an example of a basic layout file:

<template>
    <div>
        <header>
            <h1>
                {{ title }}
            </h1>
        </header>

        <main>
            <div v-if="doc">
                <div >
                    <h2>
                        {{ doc.title }}
                    </h2>
                </div>

                <ContentRenderer
                    id="nuxtContent"
                    :value="doc"
                />
            </div>
        </main>
    </div>
</template>
<script setup lang="ts">

defineProps<{
    doc: unknown;
}>()
const config = useAppConfig()
const title = config.name
</script>

The ContentRenderer component is a custom component from Nuxt Content that renders the content of the page.

Add some content

You can read the official documentation to know more about how to write articles.

But for now, you can create a new file called hello-world.md in the content folder with the following content:

---
title: Hello World
description: A simple article to say hello to the world
---

# Hello World

This is a simple article to say hello to the world.

Now, you can go to http://localhost:3000/hello-world in your browser, and you should see the article.

And voilà, you have created a new theme from scratch! You can now start customizing it to fit your needs. (it's probably ugly, but it's a start :) )

Create the layout for the tag page

Create a new file called tag.vue in the layouts/themes/neo-minimalist folder. This file will be the layout for the tag page.

Here is an example of a basic layout file:

<template>
    <div>
        <header>
            <h1>
                {{ title }}
            </h1>
        </header>

        <main>
            <div>
                <div >
                      All Posts
                </div>

              <div >
                <section class="mt-10">
                  <MinimalistListing title="All Posts" :tag="tag"/>
                </section>
              </div>
            </div>
        </main>
    </div>
</template>
<script setup lang="ts">
  defineProps<{
    tag: string;
  }>()
const config = useAppConfig()
const title = config.name
</script>

This layout will display the list of all the articles with the specified tag. To do so, we use the MinimalistListing component.

Now, you can go to http://localhost:3000/tags/lorem in your browser, and you should see the list of all the articles with the tag "lorem".

Create the layout for the category page

The layout for the category page is similar to the layout for the tag page. You can create a new file called category.vue in the layouts/themes/neo-minimalist. The only difference is that you have to replace the tag prop by the category prop.

Create a custom component to display the list of articles

Let's create a custom component to display the list of articles. Create a new file called MyOwnListing.vue in the components/content folder of your project.

<template>
  <div>
    <ul>
      <li v-for="doc in docs" :key="doc.id">
        <NuxtLink :to="`/${doc._path}`">
          {{ doc.title }}
        </NuxtLink>
      </li>
    </ul>
    <MinimalistPaginationBar :total="totalNumberOfPages"  />
  </div>
</template>
<script setup lang="ts">
const props = defineProps<{
    category?: string;
    tag?: string;
}>()

const {itemsPerPage, currentPage} = usePagination()
const id = 'listing-' + props.category + '-' + props.tag 
let where = {}
if (props.category) {
    where['categories'] = { $in: [props.category] }
}
if (props.tag) {
    where['tags'] = { $in: [props.tag] }
}
where = { ...where, ...{ draft: { $ne: true }, listed: { $ne: false } } }

const numberOfPostsPerPage = itemsPerPage.value

const { data: docs } = useAsyncData(id, () => {
    return queryContent('')
        .where(where)
        .sort({ date: -1 })
        .skip((currentPage.value - 1) * numberOfPostsPerPage)
        .limit(numberOfPostsPerPage)
        .find()
})
const totalNumberOfPages = await queryContent('').where(where).count()

</script>

Now, you can use the MyOwnListing component in the archive.vue, tag.vue, and category.vue layouts.

You can replace the MinimalistListing component by the MyOwnListing component in the archive.vue, tag.vue, and category.vue layouts.

Note the use of the usePagination composable. This hook is provided by the @bloggrify/core package.