How to Build a Corporate Design Agency Site with NuxtJS and Strapi
Tags: nuxt, node, strapi
Published: 2022-01-19 11:48:29

In this tutorial we’ll learn the benefits of a Headless CMS and create a corporate design agency site with Strapi as our headless CMS back-end and NuxtJS as our frontend.

## Introduction

Most corporate sites have been built using a traditional CMS like [WordPress](https://wordpress.org/) or [Drupal](https://www.drupal.org/). These CMSs can be seen as “monolithic” as the front-end and back-end are packed into one system. Headless CMSs like Strapi allow you to decouple the two and give you the freedom to choose however you want to build your front-end. Creating a site with pages for blogs, projects, case studies, and other content requires not only the database but also a system to easily create and manage it. Strapi handles all of that for you.

## Goals

At the end of this tutorial, we would have created a complete design agency site with all the functionality like fetching data, displaying content, and routing on the front-end (built with NuxtJS) and content managed in the back-end with Strapi. We’ll learn the benefits of a headless CMS and its real-world application in building corporate sites with any front-end of choice.

## Brief Overview of Traditional and Headless CMSs

CMS is short for *Content Management System*. A CMS allows users to manage, modify and publish content on their websites without having to know or write code for all the functionality.

For a long time organizations have been using Traditional CMS options such as [WordPress](https://wordpress.com/) or [Drupal](https://www.drupal.org/) to build their websites. Traditional CMSs are monolithic in the sense that the front-end and back-end can't run separately, they are coupled together. This limits your choice of the front-end technology to the one provided by the CMS and makes you dependent on themes provided by CMS creators or the community for customization. Although there are some advantages to using a traditional CMS, especially for some organizations that want a site ready in a short period of time without much effort. However, for modern sites and applications, the benefits of a Headless CMS far outweigh that of a traditional CMS.

What is a Headless CMS anyway? A Headless CMS is simply one where the front-end and back-end are separated from each other. This means that we can build our front-end on any stack or framework, host it anywhere and access our content in the CMS via APIs. 

Headless CMSs are gaining a lot of popularity as they allow developers to deliver content to their audience using front-end technologies of their choice.

## What is Strapi

We know what a Headless CMS is, let's talk about one - Strapi.
Strapi is a world-leading JavaScript open-source headless CMS. Strapi makes it very easy to build custom APIs either REST APIs or GraphQL that can be consumed by any client or front-end framework of choice.

Now that we know that Strapi gives us the superpower of choice, we'll see how we can easily build a corporate website using Strapi and a front-end framework of our choice - Nuxt.js.

## Prerequisites

To follow along in this tutorial, you'll need a few things:

- Basic knowledge of JavaScript
- Basic knowledge of [Vue](https://v3.vuejs.org/guide/introduction.html#what-is-vue-js) and [Nuxt.js](https://nuxtjs.org/docs/get-started/installation/)
- Node.js & npm installed, npm comes with Node.js by default now so you can download Node.js [from Node.js official site](https://nodejs.org/en/) if you haven't already.
## What we're building

We are going to build a very corporate website, nothing too fancy for an imaginary design agency - [*Designli*](https://designli.netlify.app/). 
It'll have a few pages:

- Home/Landing page
- About Page
- Blog page
- Projects page
- Project page for each project
- Contact Us page

To build this site, we need to first set up Strapi. We’ll create the collection types for the various content that will be provided for each page. For example, an article collection type for the blog, and projects collection type for the projects page.

Then, we'll build the UI using Nuxt. We'll fetch the data we need for each page from our Strapi API and display them on the site.

You can find the source code for the finished frontend here on [GitHub](https://github.com/miracleonyenma/designli-agency-site)

Alright. Let's get started.


## Step 1 - Set Up Website back-end With Strapi

Now the fun stuff. Strapi is pretty easy to get started with. You can take a look at  [Strapi’s in](https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/installation/cli.html#creating-a-strapi-project)[s](https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/installation/cli.html#creating-a-strapi-project)[tallation guide](https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/installation/cli.html#creating-a-strapi-project) for more info on how to get started.

We'll be using the quickstart flag which creates the project in the quick-start mode which uses the default *SQLite* database for the project.

In your terminal, install Strapi with the following command:

```bash
    npx create-strapi-app@latest designli-API --quickstart
```

Once Strapi has successfully been installed, the Strapi app starts automatically by default and opens up your browser to `http://localhost:1337/admin/auth/register-admin`. If this doesn’t happen for some reason, run:

```
    cd designli-API
    npm run develop
```

This builds Strapi and automatically opens up your browser to `http://localhost:1337/admin/auth/register-admin`. This shiny new Strapi v4 page contains a registration form to create an `admin` account. 
You'll use the admin account to create and manage collections and content.


![Strapi’s Admin register page](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639204461506_strapi-admin-register-page.png)


Once the admin account has been created, you'll be taken to the *admin* page at `http://localhost:1337/admin/`. This is where we'll create our collection types and content.


![Strapi Admin Dashboard Welcome Page](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639205632410_strapi-admin-page.png)


Now that we've created our Strapi app, let's add some content.

## Step 2 - Create Content Types for various content

We'll now create content types for the content of our collections on our design agency website.
Content types define the structure of our data and we can set our desired fields which are meant to contain (e.g. text, numbers, media, etc.). The collections we'll need to create for our website will include:

- A collection of articles for the website blog and categories
- A collection of projects containing images, case study text and project categories
- A collection of user-submitted content from the form on the contact us page

Let's start by creating the content types.

**Create Article Collection Content-Type**
To create a content type for our collections, we can click on the ***Create your first Content-Type*** button on the welcome page. 
You can also navigate to **[Content-Types Builder](http://localhost:1337/admin/plugins/content-type-builder)** page by clicking on the link right under **PLUGINS** in the sidebar, then, on the Content-Type builder page, click on **Create new collection type**.


![](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639205246239_clipout-of-content-builder.png)



A *Create a collection type* modal will appear where we'll create our Content-Type and fields. In the **Configurations**, we'll enter in the display name of our Content-Type - article. 
We're using the singular *article* as the display name since Strapi is going to automatically use the plural version of the display name - *articles* for the collection later on.


![](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639206023531_Strapi-collection-types-field.png)


Click on **continue** to proceed to add fields. There are a number of field types available here
The field names and types for our *article* are:

- `title`: Text, Short text
- `intro`: Text, Long text
- `slug`: UID, Attached field: title
- `body`: Rich Text
- `cover`: Media, Single media

Let's create the **Title** field. In the collection types menu, select **Text**. This opens a new modal form where you can enter the **Name** and select the type of text. We'll choose **Short Text**.


![Strapi create text field for article collection type](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639207170883_Strapi-example-create-title-text-field.png)


Then click on the ***Add another field*** button to proceed to the **Slug**, **Body** and **Cover** fields according to the name, and type specified in the list above.

Remember, select ***title*** as the attached field when creating the slug field. This will allow Strapi to dynamically generate the ***slug*** value based on the ***title.*** For example, in the content builder, if we set the article name to say “My first blog post”, the slug field will dynamically be updated to “my-first-blog-post”.


![Create slug UID field for article](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639207417688_Strapi-slug-field.png)


Now, we can create the remaining fields in similar ways.
Once we're done creating our fields, our collection type should look like this:


![Overview of article collection type](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639207963727_Strapi-save-collection-type.png)


Great! Now click on ***Save*** and the server will restart to save the changes. Once saved, we can go to the content manager page to  access our newly created collection.
In the Content Manager page,  under the ***COLLECTION TYPES*** menu in the sidebar. Select the ***article*** **collection type**.


![Newly created article collection type](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639208410615_Strapi-article-collection-content.png)


Here, we can create new articles and add some content. Before we do that though, we need to create a ***Categories*** collection type.

**Create Categories collection type**
Strapi also makes it easy to create relationships between collection types. In the articles, for instance, we want each article to be under one or multiple categories like *Announcements*, *Design*, *Tech*, *Development*, *Tips*, etc. We also want each category to have multiple articles. That's a typical *Many-to-many* relationship.

To create a new collection we follow similar steps as before, navigate to ***Content-Types Builder > Create new collection type***. In the modal, set the display name as `category` and click on ***Continue***.

Now we can create new field types. The field names and types for the categories collection are:

- `name`: Text, Short text, then, under ***advanced settings*** > select ***Required field*** and ***Unique field***
- `articles`: Relation, many to many

To create the name field, choose the ***Text*** field type, set the ***Name*** as `name`. Select ***Required field*** and ***Unique field*** under advanced settings. 
Once you’re done, click on ***Add another field*** to add the ***Relation field***.

To add the ***Relation field***, select ***Article*** from the drop-down menu on the right. This will automatically set the field name as ***categories***. Choose the ***many-to-many*** relationship and here's what the relation field settings look like:


![Category relation field](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639209065503_Strapi-category-relation-field.png)


Once the `name` and the `articles` fields have been created, save the collection type. We can now create new categories.

**Add new Categories**
Navigate to the content manager page and click on the ***Category*** collection type in the sidebar. Then click on the ***Add New entry*** button to create a new entry. Enter the name of the category, which is `announcements` in this case.

![Create new announcements article category](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639209444095_Strapi-create-new-category.png)


Click *Save* and then *Publish*.

We can create more categories in the same way. Here are all our categories for now:


![Overview of categories](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639351716224_Strapi-categories.png)


**Add a new Article**
To add a new article, on the content manager page, select the ***article*** collection type and click on the ***Add new entry*** button. This will open a page where we can add content to each field we created for the article collection. Let's create a new article.


![Creating an article entry](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639371319257_Strapi-create-new-article-1.png)


Here, we have the ***Title***, the ***Body*** with some markdown, the ***Cover*** image which we uploaded into our media library or assets from either our device or a URL and the ***Slug*** which is the Unique ID (UID) for our article. 

We can also select a category for our article, in the menu on the right. Here, we chose the ***announcements*** category. Once you've provided all the content, click on ***Save***. Our new article has now been saved as a draft. 
Now click ***Publish*** for the changes to be live. Here's our published article


![Published article entry](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639371343934_Strapi-saved-article.png)


Great! We can create even more articles by clicking on the *Add New Articles* button.
Let's create our next collection, Projects.

**Create Projects Collection Content-Type**
Now that we've been able to create the Articles collection type, we can follow the steps to create the Projects collection type.

On the ***Content-Type Builder*** page, click on ***Create new collection type***. Then, in the modal, set the display name as `project` then click ***continue***. Now, we have to select the fields for our collection. The fields and types for the project’s collection would be:

- `title`: Text, Short text
- `slug`: UID, Attached field: title
- `intro`: Rich Text
- `body`: Rich Text
- `cover`: Media, Single media
- `images`: Media, Multiple media

Here's what our collection type should look like:


![Overview of project collection type](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639371376538_Strapi-projects-collection.png)


Before we continue to create new projects, let’s create a categories collection type for our projects,

**Create Project Categories collection type**

Navigate to the ***Content-Type Builder*** page and click on ***Create new collection type***. 
Set the display name as - Project Category
The field names and types for the categories collection are:

- `name`: Text, Short text, then under ***advanced settings*** > select ***Required field*** and ***Unique field***
- `description`: Text, Long Text
- `cover`: Media, Single media
- `project_categories`: Relation, many to many

Select ***Project*** from the drop-down menu. This will set the field name as ***project_categories***. Choose the many-to-many relationship and here's what the relation field settings look like:


![Editing project category relationships](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639371405936_Strapi-project-category-relationship.png)


Click *Finish*, *Save* and wait for the server to restart.


![Overview of project category collection type](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639372468517_Strapi-project-categories-fields.png)


**Add new Project Categories**
Let's add new project categories like *Branding*, *Graphics*, *UI/UX*, etc. We’ll navigate to the ***Content Manager*** page and select ***project category*** under ***COLLECTION TYPES***. 

Since we’re now familiar with how to add entries to a collection type, we'll add, save and publish entries for: Branding, Graphics, UI/UX, etc. by following the steps in the previous ***Categories*** collection type. We should have something like this.


![Project category content entries](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639372978036_Strapi-project-categories-entries.png)


Great! Now let's add a new project.

**Add a new Project**
We can access our newly created ***Projects*** collection on the content manager page as ***projects*** under the ***COLLECTION TYPES*** menu in the sidebar. To add a new project, on the Content Manager page, click on the ***Add New Entry*** button. Now we can provide our project content. Here's what mine looks like:


![](/static/img/pixel.gif)

![Create project entry](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639404556144_Strapi-qfinder-project.png)



After providing all the content, click on *Save*, then click ***Publish*** for the changes to go live. Here's our published project:


![Overview of published project collection type content](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639404531541_Strapi-published-project.png)

![](/static/img/pixel.gif)


**Create a Collection of User-submitted project details**
The last collection we have to create now is for user-submitted content. So far we've been dealing with data created within Strapi, now we're going to work with data that will be created by visitors to our site and how they'll be saved to Strapi.

First, we create the collection type. Navigate to the ***Content-Types Builder*** page and click on ***Create new collection type***.

Set the display name to `visitor message`. The field names and types for the categories collection would be:

- `name` - visitor name: *Text, Short text.*
- `email` - visitor email: *Email*
- `body` - the content of the message: *Rich Text*
- `project_categories` - category of the project : *JSON*

After creating the fields, it should look like this:

![](/static/img/pixel.gif)

![Overview of visitor message collection type](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639404805623_Strapi-visitor-messages-collection.png)


Unlike the previously created collections, this will be updated from the frontend by visitors on the site. So we have to edit some permissions in order for this to work.
To be able to create new items in a collection we have to update the permissions on our Strapi ***Roles*** *and* ***Permissions*** settings.
Navigate to ***Settings*** *>* ***Roles*** *(*under *"****USERS & PERMISSIONS PLUGIN*** *") >* ***Public***. Now under ***Permissions***, click on the ***create*** checkbox to allow it***.


![](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639410533518_Strapi-visitor-messages-role-public.png)


Now we can send post requests and create new items for the *Visitor messages* collection.


## Step 3 - Test Strapi back-end API

So far we've been able to create the collection types and some content for our website back-end with Strapi. Now, we'll see how we can interact with our content using Strapi's API.

To do that we'll use an API tester like [Postman](https://www.postman.com/product/api-client/) or [Talend API Tester](https://www.talend.com/) which I use in my browser.
Let's send a request to Strapi to get our articles. To do that, we'll send a GET request to `http://localhost:1337/api/articles/`. 

With the new Strapi v4 update, we'll have to add the `api/` route in order to access the API.

However, if we send the request at this point, this is the response we'll get


    {
        "data": null,
        "error": {
            "status": 403,
            "name": "ForbiddenError",
            "message": "Forbidden",
            "details": {
            }
        }
    }

This is because, by default, Strapi prevents unauthenticated requests from accessing data. To get our data, we will have to set ***Roles*** *and* ***permissions*** for each collection type for the ***Public*** role which is the "Default role given to the unauthenticated user."

Navigate to ***Settings*** *>* ***Roles*** *(under "****USERS & PERMISSIONS PLUGIN*** *").*
Between ***Authenticated*** and ***Public*** **roles*,* select ***Public****.* 
Now under ***Permissions***, choose all allowed actions for each collection type which are count, find, and `findone`. Click on ***save***.


![Modify user permissions for collection types](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639404752498_Strapi-roles-permissions.png)


Now if we send the GET request again, we get our articles! 🚀


![successful GET request to Strapi back-end](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639403659706_Strapi-talend-articles-fetch-succesful.png)


Now that our API is working, we can build our front-end.

## Step 4 - Set up front-end with NuxtJS and TailwindCSS

NuxtJS is a front-end framework for VueJS that provides server-side rendering capabilities. We’ll be using Nuxt to build the frontend of our corporate website. With Nuxt, we’ll be able to communicate and fetch data such as blog posts from the Strapi back-end and display for visitors.
We’ll be using Nuxt v2 in this project as the current v3 is in beta and not yet production ready.

We’ll also be using tailwind for styling the application. TailwindCSS is a utility-first CSS framework that provides us with classes to style our applications without having to write a lot of custom CSS.

Before we get our hands dirty setting up a new project, I’d like to mention that the source code for frontend is available on [Github](https://github.com/miracleonyenma/designli-agency-site). You can clone the project from GitHub and follow the instructions on the `README.md` to install. Then, you can skip ahead to the part where you create your `.env` files and setup your environment variables.

If you’re following along the set up and installation, you can also get the source code from Github and paste into the designated files as you build along. That being said, let’s go!

**Install Nuxt**
To get started, in a different directory, run


    npx create-nuxt-app designli

This asks us a set of questions before installing Nuxt. Here are the options I chose for the project. 


![](https://paper-attachments.dropbox.com/s_9024339FDD4E8C3AAE8211BE13875B191938E707EF9D80F7B4C150D598AB7D09_1635686538152_Nuxtjs+installation.png)


**Install and setup TailwindCSS and Tailwind**
First, install TailwindCSS for Nuxt. You can find the installation guide of TailwindCSS for Nuxt [here](https://tailwindcss.com/docs/guides/nuxtjs). Basically, run the following command to install


    npm install -D @nuxtjs/tailwindcss tailwindcss@latest postcss@latest autoprefixer@latest

In your `nuxt.config.js` file, add package to your Nuxt build:


    // nuxt.config.js
    ...
      buildModules: [
        '@nuxtjs/tailwindcss'
      ],
    ...

After installation, create the configuration file by running:


    npx tailwindcss init

This will create a `tailwind.config.js` file at the root of your project. Follow [the instructions](https://tailwindcss.com/docs/guides/nuxtjs#configure-tailwind-to-remove-unused-styles-in-production) to remove unused styles in production.

Create a new CSS file `/assets/css/tailwind.css` and add the following


    @tailwind base;
    @tailwind components;
    @tailwind utilities;

In your `nuxt.config.js` file, add the following to define `tailwind.css` globally (included in every page)


    // nuxt.config.js
    ...
      css: [
        '~/assets/css/tailwind.css'
      ],
    ...

**Install Tailwind Typography plugin**
The Typography plugin is [according to the docs](https://tailwindcss.com/docs/typography-plugin) is "a plugin that provides a set of `prose` classes you can use to add beautiful typographic defaults to any vanilla HTML you don't control (like HTML rendered from Markdown, or pulled from a CMS)".

You can find more about the plugin and installation guide and even a demo [on the Typography plugin docs](https://github.com/tailwindlabs/tailwindcss-typography). Installation is pretty straightforward.

Install the plugin from npm:

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

Then add the plugin to your `tailwind.config.js` file:

```
    // tailwind.config.js
    module.exports = {
      theme: {
        // ...
      },
      plugins: [
        require('@tailwindcss/typography'),
        // ...
      ],
    }
```


Next, create a `.env` file in your root folder where we’ll define the `STRAPI_URL` and `STRAPI_API_URL`

```
    // .env
    STRAPI_URL=http://localhost:1337
    STRAPI_API_URL=http://localhost:1337/api

`STRAPI_API_URL` will be used to fetch data from Strapi and,
`STRAPI_URL` will be used to fetch media from Strapi
```

Then, create a new file `store/index.js` where we will store the variable and make it globally accessible

```
    // store/index.js
    export const state = () => ({
      apiUrl: process.env.STRAPI_API_URL,
      url: process.env.STRAPI_URL,
    })
```

Great! Now we can access the API URL using `$store.state.url` in our Nuxt app.

**Install**  [**@nuxtjs/markdownit**](https://www.npmjs.com/package/@nuxtjs/markdownit) **module**
One more module we need to install is the `[@nuxtjs/markdownit](https://www.npmjs.com/package/@nuxtjs/markdownit)` which will parse the mardown text from the ***Rich Text*** fields.


```bash
    npm i @nuxtjs/markdownit markdown-it-attrs markdown-it-div
```

Then in `nuxt.config.js`,

```
    // nuxt.config.js
    ...
    {
      modules: [
        '@nuxtjs/markdownit'
      ],
      markdownit: {
        runtime: true, // Support `$md()`
          preset: 'default',
          linkify: true,
          breaks: true,
          use: ['markdown-it-div', 'markdown-it-attrs'],
      },
    }
    ...
```

Now that we’ve installed everything we’ll need for the front-end, we can now run our app

```bash
    npm run dev
```

**Front-end project source code**
Going forward, I’ll highlight the key features of the front-end where we interact with and use content from Strapi. The [source code for the completed front-end](https://github.com/miracleonyenma/designli-agency-site) can be found on GitHub.
To follow along, clone the project from GitHub to access the source files.
You can also follow the instructions on the `README.md` to install and run the project. 

Once downloaded, you can set up your Strapi back-end server, run it and then start up your front-end.
Here’s what the frontend should look like when we run `npm run dev` in the frontend folder


![](https://paper-attachments.dropbox.com/s_9024339FDD4E8C3AAE8211BE13875B191938E707EF9D80F7B4C150D598AB7D09_1635829824765_Designli+site.png)


Here’s what the directory structure looks like:

```
    designli
    ├─ assets/
    │  ├─ css/
    │  │  ├─ main.css
    │  │  └─ tailwind.css
    │  └─ img/
    ├─ components/
    │  ├─ ArticleCard.vue
    │  ├─ NuxtLogo.vue
    │  ├─ ProjectCard.vue
    │  ├─ ServiceCard.vue
    │  ├─ SiteFooter.vue
    │  ├─ SiteHeader.vue
    │  └─ SiteNav.vue
    ├─ layouts/
    │  └─ default.vue
    ├─ pages/
    │  ├─ About/
    │  │  └─ index.vue
    │  ├─ Blog/
    │  │  ├─ _slug.vue
    │  │  └─ index.vue
    │  ├─ Projects/
    │  │  ├─ _slug.vue
    │  │  └─ index.vue
    │  ├─ Contact.vue
    │  └─ index.vue
    ├─ static/
    ├─ store/
    │  ├─ README.md
    │  └─ index.js
    ├─ jsconfig.json
    ├─ .gitignore
    ├─ .prettierrc
    ├─ README.md
    ├─ nuxt.config.js
    ├─ package-lock.json
    ├─ package.json
    └─ tailwind.config.js
```

From the above structure, the `pages` directory contains our pages in their respective folders e.g. Blog page - `Blog/index.vue`. 
The `<page name>/_slug.vue` files are dynamic pages that will render content for an individual entity.

## Step 5 - Fetch content in Nuxt homepage

Let’s display our *Project Categories (services), Projects,* and *Articles* on the home page. We can fetch them from our Strapi API. 
First, make sure the Strapi server is running. Go to the Strapi directory and run `npm run develop`. 

Now in our `pages/index.vue`, we’ll use the [AsyncData](https://nuxtjs.org/docs/features/data-fetching#async-data) hook which is only available for [**pages**](https://nuxtjs.org/docs/directory-structure/pages) and doesn’t have access to `this` inside the hook. Instead, it receives [the context](https://nuxtjs.org/docs/concepts/context-helpers) as its argument. 

Here, we’ll use the `fetch` API to fetch data for `projects`, `articles` and `services` 


> Some heads up…
> 
> We’ll be using the `project-categories` collection for the `services`,
> Also, in order to obtain data like images and relations, we have to specify the fields to populate in our query.
> To do that we’ll include the `?populate=*` query parameter to the URL . More info can be found in the [V4 docs](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html#relations-population)

```
    <!-- pages/index.vue -->
    ...
    <script>
      export default {
        // use destructuring to get the $strapi instance from context object
        async asyncData({ $strapi }) {
          try {
            // fetch data from strapi
            const services = await (
              await fetch(`${store.state.apiUrl}/project-categories?populate=*`)
            ).json()
            const projects = await (
              await fetch(`${store.state.apiUrl}/projects?populate=*`)
            ).json()
            const articles = await (
              await fetch(`${store.state.apiUrl}/articles?populate=*`)
            ).json()
            
            // make the fetched data available in the page
            // also, return the .data property of the entities where
            // the data we need is stored
            return {
              projects: projects.data,
              articles: articles.data,
              services: services.data,
            }
          } catch (error) {
            console.log(error)
          }
        },
      }
    </script>
```

We will pass in this data as `props` to our components later on.

## Step 6 - Displaying our data

We have three main components that display our content - `ArticleCard`, `ServiceCard` and `ProjectCard`. 

**The ArticleCard component**
In this component we obtain the data passed down through props. Then display the *Title*, *Intro* and *Cover.* 
To get the cover images, we combine the Strapi URL (`STRAPI_URL`) in `$store.state.url` to the relative URL (`/uploads/medium_<image_name>.jpg`) gotten from `article.cover.formats.medium.url`. 
The `src` value should now look something like this when combined: `http://localhost:1337/uploads/medium_<image_name>.jpg`.

To obtain this new URL, we’ll use a computed property:

```
    <script>
      export default {
        props: ['article'],
        computed: {
          // computed property to obtain new absolute image URL
          coverImageUrl(){
            const url = this.$store.state.url
            const imagePath = this.article.cover.data.attributes.formats.medium.url
            return url + imagePath
          }
        }
      }
    </script>

    <!-- components/ArticleCard -->
    <template>
      <li class="article md:grid gap-6 grid-cols-7 items-center mb-6 md:mb-0">
        <div class="img-cont h-full overflow-hidden rounded-xl col-start-1 col-end-3">
          <!-- fetch media from strapi using the STRAPI_URL + relative image URL -->
          <img :src="coverImageUrl" alt="">
        </div>
        <header class=" col-start-3 col-end-8">
          <h1 class="font-bold text-xl mb-2">{{article.title}}</h1>
          <p class="mb-2">{{article.intro}}</p>
          <!-- link to dynamic page based on the `slug` value -->
          <nuxt-link :to="`/blog/${article.slug}`">
            <button class="cta w-max">Read more</button>
          </nuxt-link>
        </header>
      </li>
    </template>
    <script>
      export default {
        props: ['article'],
        computed: {
    
          // computed property to obtain new absolute image URL
          coverImageUrl(){
            const url = this.$store.state.url
            const imagePath = this.article.cover.data.attributes.formats.medium.url
            return url + imagePath
          }
        }
      }
    </script>
```

**The ServiceCard component**
In this component, data is obtained through props. We then display the *Name* and *Description.* the image is obtained similarly to the last component. 

```
    <!-- components/ServiceCard -->
    <template>
      <li class="service rounded-xl shadow-lg">
        <header>
          <div class="img-cont h-36 overflow-hidden rounded-xl">
            <img v-if="coverImageUrl" :src="coverImageUrl" alt="" />
          </div>
          <div class="text-wrapper p-4">
            <h3 class="font-bold text-xl mb-2">{{service.name}}</h3>
            <p class="mb-2">
              {{service.description}}
            </p>
          </div>
        </header>
      </li>
    </template>
    <script>
    export default {
      props: ['service'],
      computed: {
        coverImageUrl(){
          const url = this.$store.state.url
          const imagePath = this.service.cover.data.attributes.formats.medium.url
          return url + imagePath
        }
      }
    }
    </script>
    <style scoped> ... </style>
```

**The ProjectCard component**
In this component, to display the project categories of the project in a comma separated string, we map through the `project_categories` property and return an array of the name value. 
Let’s use a computed property for this

```
    ...
    computed: {
      ...
      projectCategories(){
        return this.project.project_categories.data.map(
          x=>x.attributes["name"]
        ).toString()
      }
    }


    <!-- components/ArticleCard -->
    <template>
      <li class="project grid gap-4 md:gap-8 md:grid-cols-7 items-center mb-8 md:mb-12">
        <header style="height: min-content;" class="md:grid md:col-start-5 md:col-end-8">
          <h1 class="text-xl md:text-3xl font-bold">{{project.title}}</h1>
          <p>{{project.intro}}</p>
          <!-- map through the project categories and convert the array to string -->
          <!-- to display categories seperated by commas -->
          <p class="text-gray-600 text-sm mb-2">{{ projectCategories }}</p>
          <nuxt-link :to="`/projects/${project.slug}`">
            <button class="cta w-max">View Project</button>
          </nuxt-link>
        </header>
        <div
          class="img-cont rounded-xl h-full max-h-40 md:max-h-72 row-start-1 md:col-start-1 md:col-end-5 overflow-hidden">
          <img v-if="coverImageUrl" :src="coverImageUrl" alt="">
        </div>
      </li>
    </template>
    <script>
      export default {
        props: ['project'],
        computed: {
          coverImageUrl(){
            const url = this.$store.state.url
            const imagePath = this.project.cover.data.attributes.formats.medium.url
            return url + imagePath
          },
          projectCategories(){
            return this.project.project_categories.data.map(
              x=>x.attributes["name"]
            ).toString()
          }
        }
      }
    </script>
    <style scoped> ... </style>
```

Next, to display the data from these components, we’ll import our components into `pages/index.vue` component. We’ll loop through the data using `v-for` to render the component for each item in the data array and pass its respective props.

```
    <!-- pages/index.vue -->
    ...
    <section class="site-section services-section">
      <div class="wrapper m-auto py-12 max-w-6xl">
        <header class="relative grid md:grid-cols-3 gap-6 z-10 text-center"> ... </header>
        <ul class="services grid md:grid-cols-3 gap-6 transform md:-translate-y-20" >
          <!-- service card component -->
          <service-card 
            v-for="service in services" 
            :key="service.id" 
            :service="service.attributes" 
          />
        </ul>
      </div>
    </section>
    <section class="site-section projects-section">
      <div class="wrapper py-12 m-auto max-w-4xl">
        <header class="text-center mb-6"> ... </header>
        <ul v-if="projects" class="projects">
          <!-- project card component -->
          <project-card 
            v-for="project in projects" 
            :key="project.id" 
            :project="project.attributes" 
          />
        </ul>
        <div class="action-cont text-center mt-12">
          <nuxt-link to="/projects">
            <button class="cta">View more</button>
          </nuxt-link>
        </div>
      </div>
    </section>
    <section class="site-section blog-section">
      <div class=" wrapper py-12 md:grid gap-8 grid-cols-7 items-center m-auto max-w-6xl">
        <header style="height: min-content" class="md:grid col-start-1 col-end-3 mb-8">
          ...
        </header>
        <ul v-if="articles" class="articles md:grid gap-6 col-start-3 col-end-8">
          <!-- article card component -->
          <article-card 
            v-for="article in articles" 
            :key="article.id" 
            :article="article.attributes" 
          />
        </ul>
      </div>
    </section>
    ...
```

Here’s an example of the data being displayed with the `ServiceCard` component


![](https://paper-attachments.dropbox.com/s_9024339FDD4E8C3AAE8211BE13875B191938E707EF9D80F7B4C150D598AB7D09_1635834808246_Designli+services.png)


Sweet!

We can also display all this data in a page. For example, for the **Projects** page - `pages/Projects/index.vue`,

```
    <!-- pages/Projects/index.vue -->
    <template>
      <main>
        <header class="px-4 mb-12">
          <div class="wrapper mt-28 m-auto max-w-6xl">
            <h1 class="hero-text">Our Projects</h1>
            <p>See what we've been up to</p>
          </div>
        </header>
        <ul class="m-auto px-4 max-w-5xl mb-12">
          <project-card v-for="project in projects" :key="project.id" :project="project.attributes" />
        </ul>
      </main>
    </template>
    <script>
    export default {
      async asyncData({ store }) {
        try {
          // fetch all projects and populate their data
          const { data } = await (
            await fetch(`${store.state.apiUrl}/projects?populate=*`)
          ).json()
          return { projects: data }
        } catch (error) {
          console.log(error)
        }
      },
    }
    </script>
```

Since this is a page, we can use the `asyncData` hook to fetch project data using `$strapi`. We then pass the data as props to each component. 

Here’s what the project page looks like:

![](https://paper-attachments.dropbox.com/s_9024339FDD4E8C3AAE8211BE13875B191938E707EF9D80F7B4C150D598AB7D09_1635835303665_Designli+Project+page.png)



## Step 7 - Fetching and displaying content in Individual pages

So far we’ve been fetching collections as a whole and not individual items of the collection. 
Strapi allows us to fetch a single collection item by its **id** or **parameters.** Here are [available endpoints](https://strapi.io/documentation/developer-docs/latest/developer-resources/content-api/content-api.html#endpoints) from the Strapi docs

To display the content of individual items of our collections e.g an *article* from *Articles,* we can create and set up [dynamic pages in Nuxt](https://nuxtjs.org/docs/directory-structure/pages#dynamic-pages). In the `pages/Blog/` directory, we have a `_slug.vue` file. This will be the template for each of our articles.

**Fetch content using parameters**
We’ll fetch our data using the `asyncData()` hook. 
We’ll use the *Slug* property of the article collection item to fetch the data. 
In `asyncData()` we can get the access to the value of the URL in the address bar using `context` with `params.slug`

To do this, we have to use query parameter [*Filters*](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html#filters). For example, in order to fetch data of an article with a `slug` of `"``my-article``"`, we’ll have to use this route:


    http://localhost:1337/api/articles?filters\[slug\][$eq]=my-article&populate=*

Notice the `filters` parameter with the square brackets `[]`. The first bracket tells Strapi what field it should run the query against, the second bracket holds the operator which defines the relationship i.e `$eq` - `equal to`, `$lt` - `less than` etc. 
You can explore more operators and what they do [here](https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html#filters)

```
    ...
    // use destructuring to get the context.params and context.store
    async asyncData({ params, store }) {
      try {
        // fetch data by slug using Strapi query filters
        const { data } = await (
          await fetch(
            `${store.state.apiUrl}/articles?filters\[slug\][$eq]=${params.slug}&populate=*`
          )
        ).json()
        return { article: data[0].attributes }
      } catch (error) {
        console.log(error)
      }
    },
    ...
```

**Rendering markdown with** `@nuxtjs/markdownit`
After getting our project data, we can now display it in our `<template>`. Remember that we also have a *Body* field in our *Project* Collection. This *Body* field holds data in markdown format. To render it to valid HTML, we will use the global `$md` instance provided by `@nuxtjs/markdownit` which we installed and set up previously.

We will then style the rendered html using the Tailwind Typography `.prose` classes


```    ...
    <article class="prose prose-xl m-auto w-full">
      ...
      <div v-html="$md.render(article.body)" class="body"></div>
    </aticle>
    ...
```

The  `pages/Blog/_slug.vue`  code would look like:

```
    <!-- pages/Projects/_slug.vue -->
    <template>
      <main>
        <div v-if="article">
          <header class="">
            <div class="cover img-cont h-full max-h-96">
              <img v-if="coverImageUrl" class="rounded-b-2xl" :src="coverImageUrl" alt="" />
            </div>
          </header>
          <div class="cont relative bg-gray-50 p-12 z-10 m-auto max-w-6xl rounded-2xl">
            <article class="prose prose-xl m-auto w-full">
              <span style="margin-bottom: 1rem" class=" uppercase text-sm font-thin text-gray-600">from the team</span>
              <h1 class="hero-text mt-4">{{ article.title }}</h1>
              <p>{{ article.intro }}</p>
              <p class="text-gray-600 text-sm mb-2"><span class="font-extrabold">Categories: </span> {{ articleCategories }}</p>
              
              <!-- use markdownit to render the markdown text to html -->
              <div v-html="$md.render(article.body)" class="body"></div>
            </article>
          </div>
        </div>
        <div v-else class="h-screen flex items-center justify-center text-center">
          <header class="">
            <h1 class="hero-text">Oops..</h1>
            <p>That article doesnt exist</p>
          </header>
        </div>
      </main>
    </template>
    <script>
    export default {
      async asyncData({ params, store }) {
        try {
          // fetch data by slug using Strapi query filters
          const { data } = await (
            await fetch(
              `${store.state.apiUrl}/articles?filters\[slug\][$eq]=${params.slug}&populate=*`
            )
          ).json()
          return { article: data[0].attributes }
        } catch (error) {
          console.log(error)
        }
      },
      computed: {
        coverImageUrl() {
          const url = this.$store.state.url
          const imagePath = this.article.cover.data.attributes.formats.medium.url
          return url + imagePath
        },
        articleCategories() {
          return this.article.categories.data
            .map((x) => x.attributes['name'])
            .toString()
        },
      },
    }
    </script>
```

And here’s a screenshot of the output:

![](https://paper-attachments.dropbox.com/s_9024339FDD4E8C3AAE8211BE13875B191938E707EF9D80F7B4C150D598AB7D09_1635840774767_Strapi+blog+post.png)


We can also do the same thing for project pages, here’s the [code for the project pages on GitHub](https://github.com/miracleonyenma/designli-agency-site/blob/master/pages/Projects/_slug.vue).
That’s about it for displaying content. Next, we’ll see how we can send data to Strapi.

## Step 8 - Sending content to Strapi

n the Contact Us page - `[pages/Contact.vue](https://github.com/miracleonyenma/designli-agency-site/blob/master/pages/Contact.vue)`, we have a form where we get the data with two-way binding: `v-model` like so:

```
    <input type="text" id="name" v-model="name" value="Miracleio"  required/>
```

We’ll do this for each input field so that we have a data property for each input value with some defaults if we like:

```
    ...  
    export default {
      data(){
        return{
          success: false,
          name: 'Miracle',
          company: 'Miracleio',
          email: 'mio@mio.co',
          services: ['branding'],
          message: 'What\'s up yo?'
        }
      },
    ...
    }
```

We then attach a submit event listener to our form: 

```
    <form ref="form" @submit.prevent="submitForm()">
```

The `submitForm()` method takes the data and sends it to Strapi using the `create` method. Which takes the entity or collection name as the first argument and the the data as the second - `$strapi.create('visitor-messages', data)`

```
    ...  
    export default {
      data(){
        return{
          success: false,
          name: 'Miracle',
          email: 'mio@mio.co',
          services: ['branding'],
          message: 'What\'s up yo?'
        }
      },
      methods: {
        async submitForm(){
          const data = {
            name: this.name,
            email: this.email,
            project_categories: this.services,
            body: this.message
          }
          try {
            // send a POST request to create a new entry
            const msgs = await fetch(`${this.$store.state.apiUrl}/visior-messages`, {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({data})
            })
            if(msgs) this.success = true
          } catch (error) {
            console.log(error);
          }
        }
      }
    }
```

Now if we fill the form and submit it, a new item gets added to our *Visitor messages* collection.


![](https://paper-attachments.dropbox.com/s_2856D6E4995C278FB785D7982E9AADAB2DFFBBB6203E4F974C1F705201BA2458_1639490016303_Strapi-new-visitor-message.png)

## Conclusion

So far we’ve seen how we can create and manage content for our website with Strapi and how to access the content from the front-end. 
We created a few collection types:

- Articles
- Categories (for articles)
- Projects
- Project categories (also services)
- Visitor messages

In order to get the content of these collections, we also had to modify the **roles** and **permissions** of the public or unauthenticated user.

For the frontend, We built it with NuxtJS, made use of a few packages like `markdown-it` for example to work with the ***Rich Text*** content type.
The following pages were built:

- Home/Index page - Using components to fetch data in different sections
- Blog - fetching content from `articles` collection
- Projects - fetching content from `projects` collection
- Services - fetching content from `Project categories` collection
- Contact - Using a form to send data to the `Visitor messages` collection

As mentioned earlier, you can get the entire source code for the front-end [from the GitHub repo](https://github.com/miracleonyenma/designli-agency-site).
We can use any technology stack of our choice to interact with a Headless CMS so that we can build modern and flexible applications.


## Resources & further reading

Here are some resources that might help you going forward

- Frontend source code on GitHub - https://github.com/miracleonyenma/designli-agency-site
- [Getting Started with Nuxt](https://nuxtjs.org/docs/get-started/installation)
- [Deploying a Strapi API on Heroku in 5 min](https://strapi.io/blog/deploying-a-strapi-api-on-heroku)
- [Deploying your Nuxt app to Netlify - Debbie O'Brien](https://debbie.codes/blog/nuxt-deploying-to-netlify)


Link to code repository - https://github.com/miracleonyenma/designli-agency-site