GSD
Nuxt Tutorial: Building a Knowledge Base Powered by ButterCMS
Posted by Miracle Onyenma on October 4, 2023
Access to information is an important factor to consider when building a product or service. A knowledge base will come in handy for customers and even employees who need to navigate around a product or service themselves and find answers to questions without contacting a support agent.
The relevance of a knowledge base can be demonstrated in several scenarios. Take, for example, a customer service representative at an electronics retailer. A customer who recently purchased a device and encounters issues with it can reach out to the service representative who, in turn, can use the knowledge base to find a solution to the customer’s issue by entering keywords similar to the issue and looking up relevant articles. Similarly, if the knowledge base is open to the customer, the customer can easily find a solution to their issue themselves without having to contact customer service.
In this Nuxt tutorial, we’ll take a look at how we can quickly and easily build a knowledge base with ButterCMS, our headless CMS which will contain our KB (knowledge base) articles and FAQs, and Nuxt 3, a frontend framework with SSR (server-side rendering) features which comes with many benefits when building sites.
Table of contents
Knowledge base fundamentals
Let’s quickly look at what a knowledge base is and why a company should utilize one. A knowledge base is more or less a central hub of structured information about a specific product, service, or similar—likely collected from multiple sources and organized for easy retrieval of relevant information. A knowledge base can contain information stored in a number of formats, from articles to how-to guides to FAQs to videos.
Importance & benefits
A knowledge base can be an important tool for any organization that needs to store and access large amounts of structured information. Some of the key benefits of a knowledge base include:
- Better customer service: A knowledge base can be used by customer service representatives or even customers themselves to quickly find answers to common customer questions, which can improve the overall customer experience.
- Consistency: A knowledge base can help to ensure that all employees have access to the same information, which can help to reduce the risk of miscommunications and errors.
- Reduced training costs: A knowledge base can be used as a training resource for new employees, which can help to reduce the cost and time associated with training.
- Improved knowledge management: A knowledge base can help a company to better organize and manage its knowledge, which can make it easier to access and share information within the organization.
Now that we’re familiar with what a knowledge base is and its importance, in the following sections, we’ll be looking at the technologies we can use to build our own knowledge base.
Why use Nuxt?
First of all, Nuxt is a meta framework built on Vue.js that offers features focused on delivering an optimized user experience. One vital feature is on-demand rendering which lets developers decide what rendering strategy to use, which includes server-side rendering (SSR), static site generation (SSG), etc. Let’s look at some of the features offered by Nuxt and how they might be beneficial to us.
Server-Side Rendering
With Nuxt, applications can leverage its SSR features which allow HTML files to be generated and sent to the browser. This results in faster load times and a better user experience as content is already loaded on the page even before hydration (where the client loads the JavaScript code for interactivity) kicks in.
SEO benefits
Thanks to SSR, Nuxt websites are indexable by search engines and can be crawled by their bots for relevant content which helps in SEO ranking.
Developer experience
Nuxt offers a great developer experience due to its many features offered out of the box, which include automatic routing, auto-imports, simple state management using composables, and more.
Zero-configuration
Nuxt applications do not require extensive configuration to work. During installation, it sets up a simple template project with all the relevant files, structures, and packages to work. You can then start modifying the files to suit your project needs.
API routes/middleware
Due to its server-side capabilities, Nuxt features server/API routes that handle API requests for server routes and return data as JSON like a regular backend API. With this feature, we can extend the functionality of our website and enjoy some backend features without having to set up a separate backend server.
Community support
With a GitHub star count of 55,000 (and counting, at the time of writing), the Nuxt community, together with the Vue community, is pretty impressive and very active.
Why use ButterCMS?
ButterCMS is a headless CMS which, simply put, is a content management system (CMS) that acts as your content backend, or content API, that allows you to create and manage content. The content is made available via an API, and developers can build their own custom front-end applications to consume and display the content as needed for websites and applications.
But why use ButterCMS? Let's look at a few reasons:
- Easily accessible via API: With the Content API, we can build experiences for a range of applications and devices like the web, mobile, etc.
- Easy to use by content writers and marketers: ButterCMS comes with multiple editing and collaboration features which greatly improve its ease of use for both content writers and marketers.
- Low cost: Due to the fact that ButterCMS requires no hosting, deployment, or database integration, there are no costs associated with building, hosting, or deployment for the CMS backend.
- Flexible content modeling: Structuring and creating content that perfectly suits the product or service you’re trying to build is made incredibly easy with ButterCMS due to many of its content modeling features. A few of these include components, page types, collections, a blog engine, and more.
In the following sections, we’ll get into the interesting part of this article where we walk through how to build a working knowledge base with Butter and Nuxt.
Nuxt tutorial prerequisites
To follow along, you should have the following:
- A basic understanding of HTML, CSS, JS, and Vue
- Node.js (latest LTS version)
- Visual Studio Code
- Volar Extension and either enable Take Over Mode (recommended) or add TypeScript Vue Plugin (Volar)
- A ButterCMS account—get started here
- A terminal—I recommend using VS Code’s integrated terminal
Configuring our knowledge base content in ButterCMS
First, navigate to your Butter dashboard, and then to Settings in order to obtain our Read API Token.
Save this API Token as we’ll use it to connect our Nuxt app to ButterCMS later on.
Now, let's build out the content structure in our Butter dashboard. First, we have to set up our knowledge base article page type.
Set up a knowledge base article page type
Navigate to the page type page by clicking on the “+” icon on the Page Types option on the Content Types drop-down from the side menu.
Now, we can create our page-type structure, which includes:
- A short text field with the name “Name” and the following attributes:
- Required - ✅ True
- A Long text field with the name “Description” and the following attributes:
- Required - ✅ True
- A WYSIWYG field with the name “Content” and the following attributes:
- Required - ✅ True
With that, we should have something like this:
Now, we can save it by clicking on the Create Page Type button at the top of the page and naming our page type “KB Article”.
Now, click on Save as Page Type to save the page type.
Next, we’ll create a few collection types, one for our knowledge base categories and another for our knowledge base FAQs.
Create knowledge base category page type
We’ll create a category page type which will allow us to create category pages to categorize our knowledge base article.
To create it, once again, we’ll navigate to the page type page by clicking on the “+” icon on the Page Types option on the Content Types drop-down from the side menu. Now, create a page structure/schema of our category page by adding the following fields:
- A short text field with the name “Name” and the following attributes:
- Required - ✅ True
- A long text field with the name “Description” and the following attributes:
- Required - ✅ True
- A reference field with the name “Articles” and the following attributes:
- What will this reference - “KB Articles”
- Reference type - One-to-many
With that, we should have something like this:
Now click on Create Page Type to name and save our page type. Here, we’ll name it “KB Category”.
Click on Save as a Page Type to save it.
Create a FAQ collection type
Navigate to the collection type page by clicking on the “+” icon on the Collections option on the Content Types drop-down from the side menu.
Now, we can create our page-type structure which includes:
- A short text field with the name “Question” and the following attributes:
- Required - ✅ True
- A WYSIWYG field with the name “Answer” and the following attributes:
- Required - ✅ True
- A reference field with the name “Category” and the following attributes:
- What will this reference - “KB Category”
- Reference type - One-to-one
With that, we should have something like this:
To save, click on the Create Collection button at the top of the page and enter “KB FAQs” as the Collection Name.
Click on Save as Collection to save the collection. Note how we added the Category reference to our KB FAQs collection, we’ll be doing the same for our KB Articles page type so that we can be able to categorize our articles and FAQs.
Add a category reference to the KB Articles page type
Navigate to the collection type page by clicking on the Page Types option on the Content Types drop-down from the side menu.
Now, click on the KB Article page type to edit it.
Add a new reference field with the name “Category” and the following attributes:
- What will this reference - “KB Category”
- Reference type - One-to-one
It should look something like this now:
With that, click on the Save button to save the changes. Now, we can proceed to create our content.
Create and add content to knowledge base pages
Now, let’s create a few pages for our knowledge base. To create a new page, you can click on the “+” icon on the KB Article option on the Pages menu item on the sidebar. Or, navigate to the /pages page and click on the New page button at the top right of the page and select the KB Article option.
Next, we’ll enter the page metadata of our new KB article, the page title, and the API slug (automatically populated).
Next, we’ll enter the name, description, and content of our article:
Now, save and publish the article by clicking on the Publish button. Follow the same steps to create and publish more articles.
Create categories
Now, let’s create a few categories for our knowledge base articles. To create a new page, you can click on the “+” icon on the KB Category option on the Pages menu item on the sidebar. Or, navigate to the /pages page and click on the New Page button at the top right of the page and select the KB Category option:
Now, we’ll enter the page metadata of our new KB category, the page title, and the API slug (automatically populated). Here, we’re naming it “Customer Support”.
Click on Save Page Metadata to save it.
Now, we enter the name, description, and article references to previously created KB articles. To add article references, click on the Add Reference button and select the articles that should belong to the category:
Once selected, click on Use Selected to add the selected articles to the reference.
Now, click on the Publish button to save and publish the category. Repeat the steps to create multiple categories and article references.
Create FAQs
Now, let’s create a few FAQs for our knowledge base. To create a new collection, you can click on the “+” icon in the KB FAQs option in the Collections menu item on the sidebar. Or, navigate to the /collections page and click on the New collection button at the top right of the page and select the KB FAQs option:
In the Add Item to KB FAQs page, enter the question and answer for the FAQ and category references to previously created KB categories.
To add article references, click on the Add Reference button and select the category that the FAQ belongs to. Once selected, click on Use Selected to add the selected articles to the reference.
Once satisfied, click on the Publish button to save and publish the FAQ. Follow the steps to create more FAQs.
Once that’s all done and we have our content set up, we can create our Nuxt front-end and integrate ButterCMS.
Setting up our Nuxt application
To install Nuxt, navigate to a folder of your choice, open up a terminal window, and run the command:
npx nuxi init nuxt-butter-kb
Once the project has been created, navigate to the new nuxt-butter-kb folder and install dependencies by running the command:
cd nuxt-butter-kb
npm install
Set up Tailwind and Tailwind plugins
Now, we’ll set up Tailwind and install a few plugins, namely: Tailwind Typography and Tailwind Forms.
npm install -D tailwindcss postcss autoprefixer @tailwindcss/typography @tailwindcss/forms
Once installed, run the following command to initialize Tailwind:
npx tailwindcss init
This will also generate a tailwind.config.ts file. After that, we’ll also have to configure our template paths where Tailwind will look for classes and specify our typography and forms plugins. Our ./tailwind.config.js
file looks like below:
// ./tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./nuxt.config.{js,ts}",
"./app.vue",
],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/typography'),
require('@tailwindcss/forms'),
],
}
Next, we’ll create an ./assets/css/main.css
file and add the @tailwind
directives:
// ./assets/css/main.css
@tailwind base;
@tailwind components;
@tailwind utilities;
Next, we’ll add the CSS file and Tailwind to our PostCSS configuration in our ./nuxt.config.ts
file:
// ./nuxt.config.ts
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
css: ['~/assets/css/main.css'],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
})
Connecting our Nuxt App to Butter
Create a new ./.env
file to store our Butter Read API Token we copied from our dashboard:
// ./.env
BUTTER_READ_API_TOKEN=<TOKEN>
Next, we’ll set up the Nuxt runtime config to expose configuration and environment variables within our application. In ./nuxt.config.ts
:
// ./nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// The private keys which are only available within server-side
butterApiToken: process.env.BUTTER_READ_API_TOKEN,
},
// ...
})
Finally, let's install the ButterCMS SDK which will help us communicate with the Butter API.
npm install buttercms
In the next section, we’ll create a few functions that will help us fetch the data we need from Butter.
Create Butter helper functions
In a new file, ./utils/butter.ts
, we’ll create a few functions that will handle data fetching for article pages, category pages, and FAQs collections:
// ./utils/butter.ts
import Butter from "buttercms";
const { butterApiToken } = useRuntimeConfig();
// initialize butter
export const butter = Butter(butterApiToken);
// function to fetch categories
export const getCategories = async (params?: any) => {
try {
const res = await butter.page.list("kb_category", params);
return res?.data;
} catch (error) {
console.log(error);
return null;
}
};
// function to fetch category by slug
export const getCategory = async (slug: any) => {
try {
const res = await butter.page.retrieve("kb_category", slug);
return res?.data;
} catch (error) {
console.log(error);
return null;
}
};
// function to get articles
export const getArticles = async (params?: any) => {
try {
const res = await butter.page.list("kb_article", params);
return res?.data;
} catch (error) {
console.log(error);
return null;
}
};
// function to get article by slug
export const getArticle = async (slug: any) => {
try {
const res = await butter.page.retrieve("kb_article", slug);
return res?.data;
} catch (error) {
console.log(error);
return null;
}
};
// function to get FAQs
export const getFAQs = async (params?: any) => {
try {
const res = await butter.content.retrieve(["kb_faqs"], params);
return res?.data;
} catch (error) {
console.log(error);
return null;
}
};
// function to get search results
export const getSearchResults = async (query?: any, params?: any) => {
try {
const res = await butter.page.search(query, params);
return res?.data;
} catch (error) {
console.log(error);
return null;
}
};
Here, we import Butter and initialize it with our butterApiToken
from our runtime config. Next, we create the helper functions:
getCategories()
- This function fetches pages of thekb_category
page type using thebutter.page.list()
method which allows us to get multiple pages of a specified page type.getCategory()
- This function fetches a single page of thekb_category
page type using thebutter.page.retrieve()
method which allows us to get a single page of a specified page type.getArticles()
- Similar togetCategories()
, this function fetches pages of thekb_article
.getArticle()
- Also similar togetCategory()
, this function fetches a single page of thekb_article
page type.getFAQs()
- This function fetches thekb_faqs
collection using thebutter.content.retrieve()
method.getSearchResults()
- This function fetches pages that match the specified search query using thebutter.page.search()
method.
Also, notice that each function accepts a params argument which is an object of parameters passed to butter for filtering and pagination.
Now that our functions are ready, we can begin using them in our server API routes which allow us to make server-side requests in Nuxt to the ButterCMS API.
Create server API routes to fetch data
We can set up our server API routes in Nuxt by creating new files in the ./server/api/
directory
. Each file created here will match the API route for its file name. For example, if we create the file ./server/api/hello.ts
, we can access that API on localhost:3000/api/hello.
In the following sections, we’ll be creating each of the API routes we’ll need in our project using the functions we created earlier.
Create a route to get categories
Now, we’ll create an API route to get categories by creating a new file called ./server/api/getCategories.ts
:
// ./server/api/getCategories.ts
import { getCategories } from "~~/utils/butter";
export default defineEventHandler(async (event) => {
// get the query from the event
const query = getQuery(event);
const data = await getCategories(query);
return data;
});
Create a route to get a single category by slug
In a new file called ./server/api/getCategory.ts
, enter the following:
// ./server/api/getCategory.ts
import { getCategory } from "~~/utils/butter";
export default defineEventHandler(async (event) => {
// get the slug from query from the event
const { slug } = getQuery(event);
const data = await getCategory(slug);
return data;
});
Create a route to get articles
In a new file called ./server/api/getArticles.ts
, enter the following:
// ./server/api/getArticles.ts
import { getArticles } from "~~/utils/butter";
export default defineEventHandler(async (event) => {
// get the query from the event
const query = getQuery(event);
const data = await getArticles(query);
return data;
});
Create a route to get a single article by slug
In a new file called ./server/api/getArticle.ts
, enter the following:
// ./server/api/getArticle.ts
import { getArticle } from "~~/utils/butter";
export default defineEventHandler(async (event) => {
// get the slug from query from the event
const { slug } = getQuery(event);
const data = await getArticle(slug);
return data;
});
Create a route to get the FAQs collection
In a new file called ./server/api/getFAQs.ts
, enter the following:
// ./server/api/getFAQs.ts
import { getFAQs } from "~~/utils/butter";
export default defineEventHandler(async (event) => {
// get query from event
const query = getQuery(event);
const data = await getFAQs(query);
return data;
});
Create a route to get article search results based on a query
In a new file called ./server/api/searchArticles.ts
, enter the following:
// ./server/api/searchArticles.ts
import { getSearchResults } from "~~/utils/butter";
export default defineEventHandler(async (event) => {
// get the query from the event
const query = getQuery(event);
// get the search results from butter
// pass the query and params to the function
const data = await getSearchResults(query.query, {
page_type: query.page_type || "kb_article",
page: query.page || 1,
page_size: query.page_size || 10,
});
return data;
});
Awesome. Now that we’re done creating our server API routes, let’s create a few components.
Creating our components
Let’s create a few components that we’ll be using in our project, namely our site header and search form.
In order to maintain the readability and conciseness of the code, the styles and Tailwind classes for the components and pages in this tutorial will be placed in external CSS files. The CSS files containing the styles can be accessed in the GitHub code here.
Create the SiteHeader component
Create a new file called ./components/SiteHeader.vue
:
<!-- ./components/SiteHeader.vue -->
<template>
<header class="site-header">
<div class="wrapper">
<NuxtLink class="flex gap-2 items-center text-teal-600" to="/">
<figure class="site-logo">
<span class="font-black text-xl uppercase">GizmoGenius</span>
</figure>
<span> | Knowledge Base</span>
</NuxtLink>
</div>
</header>
</template>
<style scoped>
@import url("~/assets/css/components/SiteHeader.css");
</style>
Create the Search component
This component is simply a form that takes in a search query as input and navigates to the /routes
page with the search query using useRouter()
. Create a new file called ./components/Search.vue
:
<!-- ./components/Search.vue -->
<script setup>
const query = ref("");
const router = useRouter();
const submitForm = (e) => {
e.preventDefault();
// route to search page with query
router.push({ path: "/search", query: { query: query.value } });
};
</script>
<template>
<div class="form-cont">
<form @submit="submitForm" class="search-form">
<div class="wrapper">
<div class="form-control search">
<input
v-model="query"
name="search"
id="search"
type="text"
class="form-input"
placeholder="Enter your query"
/>
<button class="cta search">Search</button>
</div>
</div>
</form>
</div>
</template>
Now that we’ve created our components, let’s begin to create our pages.
Creating our knowledge base pages
First, we’ll start with the home page.
Create the home page
To enable routing and create our home page, we just have to create a new file in the ./pages/
directory
. For our home page, we’ll create a new ./pages/index.vue
file. On this page, we’ll be doing quite a few things. First, in the <script>
, we’ll use useAsyncData
and $fetch
to make requests to the server API endpoints we created earlier to fetch our articles, categories, and FAQs:
<!-- ./pages/index.vue -->
<script setup>
const { data, error } = await useAsyncData("home", async () => {
const categories = await $fetch("/api/getCategories?page=1&page_size=10");
const articles = await $fetch("/api/getArticles?page=1&page_size=10");
const FAQs = await $fetch("/api/getFAQs?page=1&page_size=10");
return {
categories,
articles,
FAQs,
};
});
useHead({
title: "GizmoGenius Knowledge Base",
meta: [
{
key: "description",
name: "description",
content:
"Your one-stop resource for information on our products and services.",
},
],
});
</script>
<!-- ... -->
Here, we also use useHead()
to add title metatags containing information about our page. Next, in the same ./pages/index.vue
file, in the <template>
section, we’ll enter the markdown to display our content:
<!-- ./pages/index.vue -->
<!-- ... -->
<template>
<main class="site-main">
<header class="site-hero">
<div class="wrapper">
<h1 class="font-medium text-4xl">
Welcome to GizmoGenius Knowledge Base
</h1>
<p class="text-lg">
Your one-stop resource for information on our products and services.
</p>
<!-- Search component -->
<Search />
</div>
</header>
<section class="site-section categories-section">
<div class="wrapper">
<header class="section-header">
<h2 class="text-xl text-teal-800">Top Categories</h2>
</header>
<ul v-if="data.categories" class="categories-list">
<li
v-for="category in data.categories?.data"
:key="category.slug"
class="category-item"
>
<NuxtLink :to="`/categories/${category.slug}`">
<div class="wrapper">
<h3 class="font-medium text-teal-800 text-3xl capitalize mb-2">
{{ category.name }}
</h3>
<p class="text-lg">{{ category.fields.description }}</p>
</div>
</NuxtLink>
</li>
</ul>
<span v-else>Oops... Nothing to see here</span>
</div>
</section>
<section class="site-section articles-section">
<div class="wrapper">
<header class="section-header section-header">
<h2 class="text-xl text-teal-800">Top Articles</h2>
</header>
<ul v-if="data.articles" class="articles-list">
<li
v-for="article in data.articles?.data"
:key="article.slug"
class="article-item"
>
<NuxtLink :to="`/articles/${article.slug}`">
<div class="wrapper">
<h3 class="font-medium text-teal-800 text-2xl capitalize mb-2">
{{ article.name }}
</h3>
<p class="text-lg">{{ article.fields.description }}</p>
</div>
</NuxtLink>
</li>
</ul>
<span v-else>Oops... Nothing to see here</span>
</div>
</section>
<section class="site-section faqs-section">
<div class="wrapper">
<header class="section-header">
<h2 class="text-xl text-teal-800">Top FAQs</h2>
</header>
<ul v-if="data.FAQs" class="faqs-list">
<li
v-for="FAQ in data.FAQs?.data['kb_faqs']"
:key="FAQ.meta.id"
class="faq-item"
>
<details class="wrapper">
<summary>
<h3 class="inline font-medium text-teal-800 text-xl capitalize">
{{ FAQ.question }}
</h3>
</summary>
<div class="prose p-4 max-w-4xl" v-html="FAQ.answer"></div>
</details>
</li>
</ul>
<span v-else>Oops... Nothing to see here</span>
</div>
</section>
</main>
</template>
Here, we also added our <Search />
component in the header section. Next, in order to display our page, we have to use the <NuxtPage />
component in the ./app.vue
file
:
<!-- ./app.vue -->
<template>
<div class="site">
<NuxtLoadingIndicator color="#134e4a" />
<SiteHeader />
<NuxtPage />
</div>
</template>
Here, we’re also adding the <NuxtLoadingIndicator />
and <SiteHeader />
components. Now, when we start our application and navigate to the home page, Nuxt makes the requests to the server API routes on the server side which fetches the data from the ButterCMS API. The page is then rendered server-side and sent as pre-rendered HTML with all the content to the browser.
Let’s start our application by running:
npm run dev
We should see something like this:
For the articles section:
For the FAQs section:
Awesome. Next, we’ll create our dynamic category page.
Create a dynamic category page
This page will show a category and articles under that category by its slug. First, we create a new file called ./pages/categories/[slug].vue
which is a dynamic page. We get the slug from the route parameters and use it to fetch our article. We’ll fetch the article using the /api/getCategory
server API route and pass the slug as a query parameter:
<!-- ./pages/categories/[slug].vue -->
<script setup>
const route = useRoute();
// get slug from route params
const { slug } = route.params;
const { data: category, error } = await useAsyncData(slug, async () => {
const category = await $fetch(`/api/getCategory?slug=${slug}`);
return category;
});
// set page title and meta description
useHead({
title: category.value?.data.name,
meta: [
{
key: "description",
name: "description",
content: category.value?.data.fields.description,
},
],
});
</script>
<template>
<main class="site-main">
<header class="site-hero">
<div class="wrapper">
<h1 class="font-medium text-4xl">
{{ category?.data.fields.name }}
</h1>
<p class="text-lg">
{{ category?.data.fields.description }}
</p>
</div>
</header>
<section class="site-section articles-section">
<div class="wrapper">
<ul v-if="category?.data.fields.articles" class="articles-list">
<li
v-for="article in category?.data.fields.articles"
:key="article.slug"
class="article-item"
>
<NuxtLink :to="`/articles/${article.slug}`">
<div class="wrapper">
<h3 class="font-medium text-teal-800 text-2xl capitalize mb-2">
{{ article.name }}
</h3>
<p class="text-lg">{{ article.fields.description }}</p>
</div>
</NuxtLink>
</li>
</ul>
<span v-else>Oops... Nothing to see here</span>
</div>
</section>
</main>
</template>
With that, when we navigate to a category, we should have something like this:
Next, we’ll do something similar for our dynamic articles page.
Create a dynamic articles page
Create a new file called ./pages/articles/[slug].vue
and enter the following:
<!-- ./pages/articles/[slug].vue -->
<script setup>
const route = useRoute();
// get slug from route params
const { slug } = route.params;
const { data: article, error } = await useAsyncData(slug, async () => {
const article = await $fetch(`/api/getArticle?slug=${slug}`);
return article;
});
// set page title and meta description
useHead({
title: article.value?.data.name,
meta: [
{
key: "description",
name: "description",
content: article.value?.data.fields.description,
},
],
});
</script>
<template>
<article class="site-main">
<header class="site-hero">
<div class="wrapper">
<h1 class="font-medium text-4xl">
{{ article?.data.fields.name }}
</h1>
<p class="text-lg">
{{ article?.data.fields.description }}
</p>
</div>
</header>
<div
class="prose prose-xl max-w-4xl m-auto py-12"
v-html="article?.data.fields.content"
/>
</article>
</template>
Here, we render the markup sent from the article content field using the v-html directive. We also use the prose class to add default typography styling. With that, when we view an article, we should have something like this:
Awesome. Next, we’ll create our search page.
Create search page
All we have to do in our search page is to make a request to /api/searchArticles
and pass in the search query which will be obtained from the route using:
const route = useRoute();
const { query } = route.query;
To obtain the current page route and query, create a new file called ./pages/search.vue
. In the <script>
, we obtain the search query and make a request to get the results:
<!-- ./pages/search.vue -->
<script setup>
const route = useRoute();
// get `query` from route query
const { query } = route.query;
const { data: search } = await useAsyncData("search", async () => {
const data = await $fetch(`/api/searchArticles?query=${query}`);
return data;
});
useHead({
title: `Search results for: ${query}`,
});
</script>
Then, in the same ./pages/search.vue
file, in our template we render the article results:
<!-- ./pages/search.vue -->
<!-- ... -->
<template>
<main class="site-main">
<header class="site-hero">
<div class="wrapper">
<h1 class="font-medium text-4xl">Search results for: {{ query }}</h1>
<Search />
</div>
</header>
<section class="site-section articles-section">
<div class="wrapper">
<header class="section-header section-header">
<h2 class="text-xl text-teal-800">Top Matches</h2>
</header>
<ul v-if="search.data" class="articles-list">
<li
v-for="article in search?.data"
:key="article.slug"
class="article-item"
>
<NuxtLink :to="`/articles/${article.slug}`">
<div class="wrapper">
<h3 class="font-medium text-teal-800 text-2xl capitalize mb-2">
{{ article.name }}
</h3>
<p class="text-lg">{{ article.fields.description }}</p>
</div>
</NuxtLink>
</li>
</ul>
<span v-else>Oops... Nothing to see here</span>
</div>
</section>
</main>
</template>
With that, we should have something like this:
Awesome.
Here, we've been able to build a search page by leveraging the Butter CMS search API. We take the query entered by the user, send a request to our Nuxt API route which then in turn, makes a secure request to the Butter CMS search API and returns a list of pages that matches our query.
Finally, here if what our finished application should look like:
Final thoughts
So far, we’ve been able to cover what a knowledge base is, its importance, and its benefits. We’ve also discussed the technologies we can use to build our own knowledge base: a headless CMS, Butter CMS, and a frontend framework, Nuxt. We also covered why both of them are great for building a knowledge base. Finally, we covered how we can set up ButterCMS and Nuxt to build our very own knowledge base with articles, categories, and FAQs. We were also able to add search functionality to our knowledge base.
Further reading and resources
Here are a few links I think you might find useful:
- Nuxt Docs
- ButterCMS API docs
- How to Build Performant Landing Pages with Nuxt and ButterCMS
- How to Quickly Build a Blog with Nuxt and ButterCMS
Resources
ButterCMS is the #1 rated Headless CMS
Related articles
Don’t miss a single post
Get our latest articles, stay updated!
Miracle Onyenma is a designer and front-end developer obsessed with crafting and sharing beautiful experiences. ✨