Daniel Phiri Open Source Advocate. Technical Writer and Speaker. Community Lead and Builder.

Delivering content with Strapi and Nuxt

4 min read 1269

An image of the Nuxt and Strapi logos.

Headless CMS is all the rage at the moment. It gives you the ability to decouple your backend from your frontend with the added advantage of delivering content from a single source.

People relish the idea of a flexible frontend, straying from the standard coupled days of Joomla and Drupal.

In this article, we’ll be setting up Strapi — a flexible, open-source, Headless CMS that you can deploy on your own servers, along with Nuxt.js, a powerful open source web application framework based on Vue.js for building universal web applications.

We’ll use the two to build a landing page for my radio show, Malgamves On Air.

To get started, we have to have a Vue CLI installed.

Run yarn global add @vue/cli to get it installed.

We’ll start by creating a new folder called nuxt-x-strapi.

Run cd nuxt-x-strapi to change directories so we can set up our frontend and backend in it.

Our frontend

We’re using Nuxt for our frontend.

Run yarn create nuxt-app frontend, and create a new folder called frontend.

N.B: Select Tailwind UI when asked about which UI framework you want to use.

Go into that folder and run yarn dev to start the application.

You’ll find it running at http://localhost:3000. You’ll be greeted by the usual Nuxt starter page:

An image of the Nuxt starter page.

Now that we have our frontend running, we need to se tup our project with Strapi as our backend.

Our backend

Strapi is extensible and lets us manage what we use as our database.

We’ll use the quickstart flag, --quickstart, which sets us up with a SQLite database.

Head back to nuxt-x-strapi/ and run the following command: yarn create strapi-app backend --quickstart.

At this point, a new folder called backend will be created.

An image of the Strapi login page.

We’ll be redirected to our browser where we have to set up our Strapi administrator credentials.

Don’t worry if you aren’t redirected — you can open up http://localhost:1337/admin/auth/register/ in your browser.

N.B: If you stop the Strapi service, you can start it again by running yarn develop in nuxt-x-strapi/backend.

After putting in your details, you’ll be directed to a dashboard. Here we’ll start to define our content types as an administrator.

The Strapi home page.

You’ll notice that we already have a User content type: this happens by default.

We need to define a content type that complements what we’re trying to build: a display of radio show episodes.

Click Create First Content Type and type album when asked for a name. Then click save.

Almost immediately, you’ll be asked to add fields to your content type. One after the other, create the following fields:

  • For an album name, select type Text and call it name
  • For the album description, select type Rich Text and call it description
  • For the album image, select type Media and call it image

After this, save the field types and save the album content type.

Your Strapi server will then restart, and you should see albums as a content type in the left menu of your dashboard.

We have our content type defined, but we don’t have any content to display yet.

Click on album in the left menu, then add album in the top right to add some content.

The page on Strapi prompting developers to create an entry.

Give your album a name, a description, and upload an image. Click save, and you’ll have some content in your database.

Before we can use this data or query any endpoints, we’ll have to set some access control rules.

You’ll notice that going to http://localhost:1337/albums gives you an error.

Allowing Access

Like we just saw, we can’t accept the data yet because we haven’t set permissions to enable us to do so.

By default, new API are secured in Strapi and need different rules to be specified.

We need to go over to Roles and Permissions under Plugins in the menu on the left (you can also go to http://localhost:1337/admin/plugins/users-permissions/roles).

Click on the authenticated role and check the find and findone checkboxes, then save.

Do the same thing for the public role, and save that, too.

Now, when you go to http://localhost:1337/albums, you should be able to see the results of the API call.

Adding GraphL support

Currently, we’re using REST for our API.

However, Strapi supports GraphQL and can be enabled by installing a plugin.

We’ll use GraphQL to deliver the content from Strapi and display it in our Nuxt app.

Shut down your Strapi service. Then, in project/backend, run yarn strapi install graphql.

Alternatively, you can install GraphQL in Strapi from the marketplace in Strapi admin.

When the installation is complete, go to http://localhost:1337/graphql and you’ll see your GraphQL Playground where you can test queries.

It looks a little like this:

A gif displaying how to create an album in Strapi.


Okay! Where are we now?

We’ve got our GraphQL Strapi backend working, and we’ve got content in our database. All we need to do now is connect the backend to the frontend.

Since we’re using GraphQL, we need to install Apollo in our Nuxt app.

In /nuxt-x-strapi/frontend/ run yarn add @nuxtjs/apollo graphql

After installing Apollo, we need to add the code below to our nuxt.config.js file.

modules: [  
apollo: {  
  clientConfigs: {
    default: {
      httpEndpoint: 'http://localhost:1337/graphql'

Now we need to create a file to store our GraphQL query.

Go to frontend/apollo/queries/album/, create the file, and call it albums.gql. Then we’ll paste this query in it.

query Albums {  
  albums {
    image {

All this does is get the id, name, description, and image url from our backend so we can consume this in our frontend.

In pages/index.vue we paste the following in our <script> tag.

​​import albumsQuery from "~/apollo/queries/album/albums";
​​export default {
​​  data() {
​​    return {
​​      albums: [],
​​      query: ""
​​    };
​​  },
​​  apollo: {
​​    albums: {
​​      prefetch: true,
​​      query: albumsQuery
​​    }
​​  },
​​  computed: {
​​    filteredList() {
​​      return this.albums.filter(album => {
​​        return album.name.toLowerCase().includes(this.query.toLowerCase());
​​      });
​​    }
​​  }

At the top of the file, we import our query.

We have a section for Apollo where we define the query we just imported, and another section where we filter the result from the query.

In pages/index.vue, we paste the following in our <template> tag:

    <div class="title">
      <h1>Welcome to My Radio Show</h1>
    <div class="container">
      <div  v-for="album in filteredList" v-bind:key="album" class="max-w-sm rounded overflow-hidden shadow-lg p-1">
          :src="'http://localhost:1337' + album.image.url" alt="" width="300" height="300" 
        <div class="px-6 py-4">
          <div class="font-bold text-xl mb-2">{{ album.name }}</div>
            class="text-gray-700 text-base"
          >{{ album.description }}</p>

Here we use v-for to dynamically display the items in our CMS.

We interpolate the album.name and album.description, as well as the image which we place in the src directive. When we run the frontend of the application, this is what we see:

An image of the Malgamves On Air radio show home page.


In this tutorial, we got a Nuxt app working, we set up our Strapi backend, added data to our CMS, and enabled the GraphQL API.

Now we have the the data in our Strapi CMS displaying in our Nuxt app.

You can check out the project repo on GitHub.

Hope you found this useful. Hit me up on Twitter if you have any questions.

Are you adding new JS libraries to improve performance or build new features? What if they’re doing the opposite?

There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.

LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.


LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Build confidently — .

Daniel Phiri Open Source Advocate. Technical Writer and Speaker. Community Lead and Builder.

10 Replies to “Delivering content with Strapi and Nuxt”

  1. Thank you for a great article on how to use Nuxt with Strapi + GraphQL.

    One thing. You can install GraphQL in strapi from the marketplace in the strapi admin. Then you reach the GraphQL playground via localhost:1337/graphql.

  2. Thanks for catching that Christher – making changes now. Glad you liked it by the way.

  3. Hey Stijn thanks for pointing that out! Making changes as soon as I can

  4. Hey Daniel, very appreciate your article, but unfortunately i got stuck at this problem:

    ERROR in ./pages/index.vue?vue&type=script&lang=js&
    Syntax Error: Unexpected character ‘​’ (30:0)

    28 |
    29 |
    > 30 | ​​export default {
    | ^
    31 | ​​ data() {
    32 | ​​ return {
    33 | ​​ albums: [],

    Could you help me to solve that? :/

    Best wishes

    1. Hey Ben! I can’t seem to see what the problem might be from the snippet you sent. Could you share a gist of the file? Do check that statement is in a tag though

  5. Hey Ben, I have the same problem as you, did you happen to find what was causing that problem ?


  6. To help anyone with the script tag throwing an error, here is a snippet of which i use that gets around the error:

    import Layout from ‘~/layouts/Default’;
    import placesQuery from ‘~/apollo/queries/places/place’;

    export default {
    data() {
    return {
    // Initialize an empty places variable
    places: [],
    query: ”
    apollo: {
    places: {
    prefetch: true,
    query: placesQuery
    computed: {
    // Search system
    filteredList() {
    return this.places.filter(place => {
    return place.name.toLowerCase().includes(this.query.toLowerCase())
    head: {
    title: ‘My Places’

  7. Hey, sorry for my late reply – i did solve the problem, simply copied the code again from above and paste it in. Didn’t find any reason why it doesn’t work in the first approach.

Leave a Reply