Hello 👋 Step into the world of modern blogging, where crafting a feature-rich personal blog is a breeze without the complexities of a traditional backend. This guide unveils the seamless integration of Vue.js, Dev.to API (Forem), Vue Router, Vue Store (Vuex), and Markdown-it. Let's embark on the journey to create a dynamic and captivating blogging experience.
🎨 I kept the frontend simple, leaving it as a blank canvas for you to customize according to your preferences.
Blog Demo ✨
https://dev-to-headless-vue.netlify.app
Github project ✨
https://github.com/benoitpetit/dev-to-headless-vue
- Step 1: Setting Up Your Vue.js Project
- Step 2: Update Main.js
- Step 3: Update App.vue
- Step 4: Implementing Vue Router
- Step 5: Setting Up Vuex (Vue Store)
- Step 6: Create Views
Integration example :
Blog list
Post details
Mardown & Code Style Integration ! 😎
Blog Demo ✨
https://dev-to-headless-vue.netlify.app
📢 Before delving into the code, here's a quick heads-up: make sure to check out the comments! They're like hidden gems, providing insights, tips, and maybe a joke or two. 😉 Don't miss the valuable information tucked away in the comments—they're your guide to understanding the code better. 😛
Prerequisites
Before we embark on the implementation, ensure that Node.js, NPM and Vue CLI are installed on your machine. If not, download and install them from nodejs.org and cli.vuejs.org
Step 1: Setting Up Your Vue.js Project
Begin by creating a new Vue.js project using the Vue CLI.
vue create my-personal-blog
cd my-personal-blog
Install prerequisite modules:
# Install vue-router
npm install vue-router
# Install vuex (vue-store)
npm install vuex
# Install node-fetch
npm install node-fetch
# Install markdown-it
npm install markdown-it
# Install highlight.js
npm install highlight.js
I don't think I've forgotten anything 🫣
Step 2: Update Main.js
Update your main.js
file to include router and store configurations.
import { createApp } from "vue";
import App from "./App.vue";
// added router & store
import router from "./router";
import store from "./store";
// Import styles global & highlight.js for code style block
import "./assets/styles/app.css";
import "highlight.js/styles/atom-one-dark.css";
const app = createApp(App);
const head = createHead();
app.use(store);
app.use(router);
app.use(head);
app.mount("#app");
Step 3: Update App.vue
in the App.vue
file, add the link router and the view router
<template>
<div id="app">
<header>
<nav class="logo">
<div>
<img :src="devto" alt="Logo" />
</div>
<div>
<h1>dev.to headless vue</h1>
</div>
</nav>
</header>
<div id="app">
<div class="menu">
<router-link to="/">Accueil</router-link>
<router-link to="/blog">Blog</router-link>
</div>
<router-view></router-view>
</div>
</div>
</template>
<script>
import { onBeforeMount } from "vue";
import { useStore } from "vuex";
import devto from "./assets/logo.png";
export default {
name: "App",
setup() {
const store = useStore();
onBeforeMount(() => {
store.dispatch("getBlogContent");
});
return {
devto,
};
},
};
</script>
<style>
.menu {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.menu a {
margin: 0 10px;
}
</style>
Step 4: Implementing Vue Router
Vue Router empowers us to navigate through different views in our single-page application. Let's establish three routes: Home, Blog, and Post.
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "@/views/HomeView.vue";
import BlogView from "@/views/BlogView.vue";
import PostView from "@/views/PostView.vue";
const routes = [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/blog",
name: "blog",
component: BlogView,
},
{
// Here publication takes an id as parameter
// it corresponds to the post id
path: "/post/:id",
name: "postView",
component: PostView,
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
Step 5: Setting Up Vuex (Vue Store)
Vuex manages the state of our application. In this case, we use it to store and retrieve blog posts.
import { createStore } from "vuex";
export default createStore({
state: {
blogContent: [],
},
getters: {
blogContent: (state) => {
return state.blogContent;
},
},
mutations: {
SET_BLOG(state, blogContent) {
state.blogContent = blogContent;
},
},
actions: {
async getBlogContent({ state, commit }) {
// check if blogContent is already set
// here we check that we have retrieved the list of articles from dev.to
// to avoid executing the request each time the page is reloaded
if (state.blogContent.length) return;
// fetch blog content to dev.to api and set state
const response = await fetch(
// you can also retrieve the url with an .env file
"https://dev.to/api/articles?username=benoitpetit"
);
const blogContent = await response.json();
commit("SET_BLOG", blogContent);
return blogContent;
},
},
});
Step 6: Create Views
We are creating the various views that will host the content of our articles.
src/views/HomeView.vue
<template>
<div class="home-view">
<h1>Welcome to the Home View!</h1>
<p>This is the content of the home view.</p>
</div>
</template>
<script>
export default {
name: "HomeView",
};
</script>
<style scoped>
.home-view {
text-align: center;
margin-top: 50px;
}
</style>
src/views/BlogView.vue
<template>
<div class="blog-view">
<h1>Welcome to the BlogView!</h1>
<!-- check and loop blogcontent for show -->
<div v-if="blogContent.length">
<div v-for="(content, index) in blogContent" :key="index">
<!-- here we create the link with the post id as parameter -->
<router-link :to="`/post/${content.id}`">
<h2>{{ content.title }}</h2>
</router-link>
<p>{{ content.description }}</p>
<ul>
tags:
<span v-for="(tag, index) in content.tag_list" :key="index">
{{ tag }},
</span>
</ul>
</div>
</div>
<div v-else>Loading...</div>
</div>
</template>
<script>
import { useStore } from "vuex";
import { computed } from "vue";
export default {
name: "BlogView",
setup() {
const store = useStore();
const blogContent = computed(() => store.state.blogContent);
return {
blogContent,
};
},
};
</script>
src/views/PostView.vue
<template>
<div class="post-view">
<!-- Check if the post belongs to me! important though :) -->
<div v-if="postContent.user && postContent.user.username === 'benoitpetit'">
<div class="title">
<h1>{{ postContent.title }}</h1>
</div>
<a
:href="
postContent.user ? `https://dev.to/${postContent.user.username}` : ''
"
class="author"
>
<img
class="rounded-circle"
:src="postContent.user ? postContent.user.profile_image : ''"
height="80"
alt="profil pic"
/>
<p>
{{ postContent.user ? postContent.user.name : "" }}
</p>
</a>
<div class="content-post-markdown">
<Markdown
class="content-post"
:source="postContent.body_markdown"
></Markdown>
<span class="post-id">The ID of the post is: {{ postId }}</span>
</div>
</div>
<div v-else>
<p>Sorry, you are not the author of this post</p>
</div>
</div>
</template>
<script>
import { ref, onMounted, watch } from "vue";
import { useRoute } from "vue-router";
import Markdown from "vue3-markdown-it";
export default {
name: "PostView",
components: {
Markdown,
},
setup() {
const route = useRoute();
const postId = ref(route.params.id);
const postContent = ref([null]);
// get post content we could put it in the blind too
const fetchPost = async () => {
try {
const response = await fetch(
// you can also retrieve the url with an .env file
"https://dev.to/api/articles/" + postId.value
);
const data = await response.json();
postContent.value = data;
} catch (error) {
console.error(error);
}
};
// fetch data post on mounted
onMounted(() => fetchPost());
// watch for route change
watch(
() => route.params.id,
(newId, oldId) => {
if (newId !== oldId && newId !== undefined) {
postId.value = newId;
fetchPost();
}
}
);
return {
postId,
postContent,
};
},
};
</script>
For a more advanced integration, visit my blog : 👉️ https://benoitpetit.dev/my-blog
Hello developers! 👋 Sharing this Vue.js Headless Blog setup with the dev.to API. While the code serves its purpose, it's not a masterpiece—there's room for improvement! 😅
I haven't reinvented the wheel (or the code), and I'm confident it can be refined to adhere more closely to best practices. Feel free to fork it; it's my gift to you! Let's improve it together. Remember, this isn't a final product but a robust starting point for launching your blog. Dive in, experiment, and let's create something amazing!
Happy coding! 💻✨
For more details on setting up a headless blog with the dev.to API, check out the Github project here: https://github.com/benoitpetit/dev-to-headless-vue/
🚀 Exciting updates ahead! In upcoming articles, I'll guide you through integrating @unhead/vue for enhanced SEO and improved link previews on social media for your personal headless blog on dev.to. Also, discover how to showcase your real-time follower count using a simple custom script. Stay tuned for these enhancements!
See you later! Beπ ✨
🫶 If you appreciate my posts and would like to support my work, feel free to make a donation by clicking on the Stripe Sponsor link below or by scanning the QR code. Stripe Link Sponsor
👀 See me here :
Top comments (0)