Comfortable | Docs
  • Comfortable Documentation
  • Concepts
    • Content Repositories
    • Content Types
    • Documents
    • Assets
    • Content Tree
    • Collections
    • Webhooks
    • Team
    • Locales
  • APIs
    • RESTful API
      • Endpoints
      • API Reference
        • Sorting
        • Filters
        • Localisation
        • Fields
        • Includes
        • Search
      • Query Examples
        • Collections
        • Single Documents
        • Single Assets
        • Sorting
        • Filters
        • Includes
    • Image Manipulation
  • SDKs
    • JavaScript
      • Query Options
      • Query All Documents
      • Query a Collection
      • Query a Single Document
      • Query an Alias
      • Query an Asset
      • Example: Filters
      • Example: Sorting
    • Nuxt.js
    • PHP
      • Query All Documents
      • Query Single Document
      • Query Collection
      • Query an Alias
      • Query an Asset
      • Fulltext Search
      • Query by Fields
      • Query by Type
      • Query by Id
      • Query by Tags
  • Guides
    • Vue Blog Example
  • Legal
    • Legal Notice
    • Privacy Policy
Powered by GitBook
On this page
  • Preparing the CMS – Creating content models
  • The Author Model
  • The Blogpost Model
  • About Page
  • Create some content
  • Collections & Pages: The Content Tree
  • Summary
  • Installing Vue and Dependencies
  • Coding the app
  • Getting started
  • Display a list of posts
  • Display a single post
  • The About page
  • Running and building the app
  • Bonus: Serverless deployment with Netlify
  1. Guides

Vue Blog Example

PreviousQuery by Tags

Last updated 6 years ago

In this guide we're going to demonstrate how to quickly build a basic CMS-Powered Blog with Vue.js and Comfortable.

If you're new to Vue.js, we'd recommend checking out this first.

The complete code for this tutorial is . You can also explore and play around with it on .

Preparing the CMS – Creating content models

To get started, we'll need a repository for our project and some models for the content. If you don't have a Comfortable account yet, you can . If you already have an account, . 🙂

Create a new repository for your blog. When you're done, head over to the Content Types page to create some models.

The Author Model

First things first, for any blogpost there is someone who wrote it. Therefore, we're going to create a Content Type Author.

On the Content Types page, click the green + Add Content Type button on the top to create a new type Author. Make sure you have checked Create a Collection field.

Next, add some fields for this content type. We'll need:

  • Name – Field Type: Text (Single line) Hint: You can simply rename the Title field, which is created automatically for new Content Types.

  • Avatar – Field Type: Asset

The Blogpost Model

Switch back to the Content Types page and add another type Blogpost. Again, make sure to have the field Create a Collection enabled. This time, add the following fields:

  • Title – Field Type: Text (Single line) This will be the title for any blogpost

  • Slug – Field Type: Text (Single line) We're going to create a URL for each Blogpost from this field.

  • Image – Field Type: Asset An image to show with each post.

  • Content – Field Type: Richtext This field will contain a Blogposts main content.

  • Author – Field Type: Relation As each Blogpost has an Author, we'll connect both with a relation. In the field configuration, we'll set a one-to-one relation for the field and select the Content Type Author, that we have just created.

About Page

The last Content Type we are going to create is really simple. We call it About page in this example, but keep it general so you could create standardised pages from it. Create a new type Page and skip the Checkbox Create a Collection this time. We need the following fields:

  • Title – Field Type: Text (Single line) This will be the page title

  • Content – Field Type: Richtext This field contains the main content for the page

Create some content

We need to create some content to display in the example. Let's start with some blog posts. Click the + Button on the page header and create a new document of type Blogpost.

For the first post, you'll also have to create a new Author. You can do while creating a blog post, by simply clicking the button Create new Entry on the Author field. For the next posts use Select Relations to pick the Author from a list.

For the demo, we'd recommend to have at least 3-5 pieces of content in your blog.

Also, don't forget to add the About page.

Collections & Pages: The Content Tree

As already mentioned, there is a checkbox Create a Collection when creating a new Content Type. This option will add a Collection to the Content Tree for this particular type.

Each collection comes with an individual endpoint, which makes it very easy to retrieve content or change the content output.

You can also link individual pages to the Content Tree and create an endpoint for a single page. We'll do that for the About page. Create your page the same way you created blog posts and when you're done, click the Add Link Button above the Content Tree:

Any collection or document node on the Content Tree has an individual endpoint to fetch content. If you're using the SDK, like in this example, all we need is the API ID. We'll come back to this later.

Summary

By the end of this section we've learned:

  • How to create Content Types (Models)

  • How to create Documents (Content)

  • How to create Collections and single linked Documents in the Content Tree

Installing Vue and Dependencies

We'll use the Vue CLI for this example. Run the following command in your terminal, if you haven't already installed Vue CLI.

npm install -g @vue/cli

Next, create the Vue app

vue create --default comfortable-vue-blog

Switch to the directory that was created by the CLI

cd comfortable-vue-blog

We'll use the Vue router for this project

vue add router

npm install comfortable-javascript --save

We'll also use lodash

npm install lodash --save

Coding the app

Enough preparations, start your favourite IDE let's finally start hacking. 😃

Tip: There are some great IDE extensions for Vue to provide syntax highlighting, autocompletion, etc. We'd highly recommend to check them out.

Getting started

router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import BlogPost from './views/BlogPost.vue'
import Page from './views/Page.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/blog/:slug',
      name: 'blog-post',
      component: BlogPost
    },
    {
      path: '/:slug',
      name: 'page',
      component: Page
    }
  ]
})

comfortable.js

Now we're going to set up the Comforable SDK for the app. Create a new file comfortable.js in src:

import Comfortable from 'comfortable-javascript';

export const comfortable = Comfortable.api('<Your API ID>', '<Your API KEY>');

Import this file into any component you want to use Comfortable.

To find your repositories API ID go to the Settings page. You'll also find your API Keys in Settings>API Keys.

App.vue

The App components purpose is to display a header on top of each page and to provide the <router-view /> component to display components we've defined in router.js.

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <main>
      <router-view/>
    </main>
  </div>
</template>

<style>
  @import url('<https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css>');

  #app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
  }

  #nav {
    background-color: #F5F5F5;
    color: #555555;
  }

  #nav a {
    color: #555555;
    text-decoration: none;
    display: inline-block;
    padding: .5rem;
  }

  #nav a:hover {
    text-decoration: underline;
  }

  .content-wrapper {
    max-width: 840px;
    margin: 42px auto;
    padding: 21px;
  }

  a {
    text-decoration: none;
  }
</style>

Display a list of posts

Home.vue

On our blog page we want to display all posts and a Load more button to paginate the list. Remember we told Vue to load the Home Component for the / in router.js? Now let's have a look at the Home Component:

<template>
  <div class="home content-wrapper">
    <div v-for="post in posts" :key="post.meta.id">
      <router-link :to="`/blog/${post.fields.slug}`">
        <article>
          <div class="image">
            <img :alt="post.fields.title" :src="`${post.fields.image[0].fields.file.url}?w=840&h=400&fit=crop`">
          </div>
          <h2>{{ post.fields.title }}</h2>
        </article>
      </router-link>
    </div>
    <button v-if="totalPosts > posts.length" @click="getPosts">
      {{loading ? 'Loading...' : 'Load more posts'}}
    </button>
  </div>
</template>

<script>
  import { comfortable } from '@/comfortable.js'

  export default {
    name: 'home',
    data() {
      return {
        posts: [],
        totalPosts: 0,
        loading: false
      }
    },
    methods: {
      getPosts() {
        this.loading = true;

        const options = {
          embedAssets: true,
          offset: this.posts.length
        };

        comfortable.getCollection('blogpost', options)
        .then(result => {
          this.posts.push(...result.data);
          this.totalPosts = result.meta.total;
          this.loading = false;
        })
        .catch(err => {
          this.loading = false;
          throw err;
        })
      }
    },
    created() {
      this.getPosts();
    }
  }
</script>

<style>
  article {
    margin-bottom: 42px;
    text-align: left;
  }

  article .image {
    width: 100%;
  }

  article .image img {
    max-width: 100%;
    height: auto;
  }

  article h2 {
    color: #2d2d33;
  }

  .home article {
    border: 1px solid #ccc;
  }

  .home article h2 {
    margin-left: 21px;
  }
</style>

What's happening here?

In short: In the script part, we fetch a list of post items from the API. For each post, Vue creates an article and displays its content, wrapped into a link to the single view.

By keeping track of the total number of posts stored in Comfortable, we are able to determine wether to display a Load more button. When the button gets clicked, the next set of post items gets fetched from the API.

Display a single post

Now that we have a list of posts, we want to be able to load single blogposts on a separate page. We've already used the Slug field from the Blogpost model to build links on the posts list page. As you remember in router.js, we have defined the second part of a single view URL to be a route parameter :slug. So the piece of information we need to retrieve a single post is present as this.$route.params.slug. All we have to do now, is use a filter query.

BlogPost.vue

<template>
  <div class="post" v-if="post && author">
    <article>
      <div class="image">
        <img :alt="post.fields.title" :src="`${post.fields.image[0].fields.file.url}?w=1680&h=750&fit=crop`">
      </div>
      <div class="content-wrapper">
        <h2>{{ post.fields.title }}</h2>
        <div class="content" v-html="post.fields.content.html"></div>
        <div class="author">
          <img :src="`${author.fields.avatar[0].fields.file.url}?w=30&h=30&fit=crop`" alt="author.fields.name"> Written by {{ author.fields.name }}
        </div>
      </div>
    </article>
  </div>
</template>

<script>
  import Comfortable from 'comfortable-javascript';
  import { comfortable } from '@/comfortable.js'
  import _ from 'lodash';

  export default {
    name: 'blogPost',
    data() {
      return {
        post: null,
        author: null,
      }
    },
    methods: {
      getPost() {
        const options = {
          embedAssets: true,
          includes: 1,
          filters: new Comfortable.Filter()
            .addAnd('slug', 'equal', this.$route.params.slug)
        };

        comfortable.getDocuments(options)
        .then(result => {
          this.post = result.data[0];
          this.author = _.find(result.includes.author, { meta: { id: this.post.fields.author[0].meta.id } });
        })
        .catch(err => {
          throw err;
        })
      }
    },
    created() {
      this.getPost();
    }
  }
</script>

<style>
  .post .image{
    width: 100%;
  }

  .post .image img{
    width: 100%;
  }

  .post .author img {
    margin-top: 10px;
  }

  .post .author img {
    display: inline-block;
    margin-bottom: -8px;
    margin-right: 10px;
    border-radius: 50%;
  }
</style>

The About page

At last we add the About page. We're going to create this page from the basic Content Type Page. This way you are able create several pages from the same Component and Content Type.

The important part here is, that we're fetching this page by a route parameter again, and this time we'll use the document alias that we've created in the Content Tree, instead of a filter.

Page.vue

<template>
  <div class="page" v-if="page">
    <article>
      <div class="content-wrapper">
        <h2>{{ page.fields.title }}</h2>
        <div class="content" v-html="page.fields.content.html"></div>
      </div>
    </article>
  </div>
</template>

<script>
  import { comfortable } from '@/comfortable.js'

  export default {
    name: 'page',
    data() {
      return {
        page: null
      }
    },
    methods: {
      getPage() {
        comfortable.getAlias(this.$route.params.slug)
        .then(result => {
          this.page = result;
        })
        .catch(err => {
          throw err;
        })
      }
    },
    created() {
      this.getPage();
    }
  }
</script>

Running and building the app

Happy coding!

Bonus: Serverless deployment with Netlify

Deploying your Blog with Netlify is perfect if you don't want to handle a server or webspace yourself. You'll be provided with a really fast and reliable deployment and hosting for any static website.

That's all. Your Blog will be available within seconds. 🚀 😊

The CLI will probably ask you if you'd like use the HTML5 history mode. Usually you'd want to choose 'Yes', and redirect any request to index.html. You can read more about the HTML5 History Mode in the .

Let's install the for a convenient way to interact with the API.

Let's prepare the routing with Vue Router first. We'll use the existing base route / to display a paginated list of all blogposts and create a new route /blog/:slug to display individual posts. If you want to learn more about the Vue Router, you can find their documentation here:

That's it 🙂 Run npm run serve to view the app in your browser, or npm run build to get a deployable blog from your codebase. Don't forget to handle the on your server.

to get in touch with the community and ask questions!

Using Netlify for this example is as simple as clicking this . You'll be asked to connect your GitHub or GitLab account to let Netlify create a Repository on your behalf. The Repository will include the code from this example and you can change it and play around as you like.

documentation
Comfortable JavaScript SDK
https://router.vuejs.org/
HTML5 History Mode
Join our Slack team
link
great Vue.js introduction
available on GitHub
CodeSandbox
sign up here
log in