Storyblok
Search Storyblok's Documentation
  1. Content Modeling in Vue

Content Modeling in Vue

Learn how to handle different content type and nestable blocks, render rich text, and use story references to manage content globally.

Setup

Copy this reference space, providing the intended structure to follow this guide. Make sure to update the access token.

Alternatively, in your existing space, create a new content type block article and an “Articles” folder with content. The article content type block should have the following fields:

  • title: Text
  • content: Rich text

Learn more about fields in the concept.

Also, create an article-overview content type block and create it as the root story of the “Articles” folder. This block requires no fields.

Finally, create a featured-articles nestable block with the following field:

  • articles: References

Add a new featured-articles block to the body field of the home story and select some articles to be featured.

Fetch and list all articles

Create a new src/ArticleOverview.vue file to get all stories from this new content type.

src/ArticleOverview.vue
<script setup>
import { useStoryblokApi } from '@storyblok/vue';

const storyblokApi = useStoryblokApi();
const articles = await storyblokApi.getAll('cdn/stories', {
	version: 'draft',
	starts_with: 'articles',
	content_type: 'article',
});
</script>

<template>
	<h1>Article Overview</h1>
	<ul>
		<li v-for="article in articles" :key="article.uuid">
		  <RouterLink :to="article.full_slug">
			  {{ article.content.title }}
			</RouterLink>
		</li>
	</ul>
</template>

Using the starts_with parameter, only stories from the “Articles” folder are fetched. Using the content_type parameter, the results are restricted to stories of the content type article.

Learn more about parameters and filter queries in the Content Delivery API documentation.

Add this to your router configuration.

src/main.js
import { createApp } from 'vue';
import { StoryblokVue, apiPlugin } from '@storyblok/vue';
import { createWebHistory, createRouter } from 'vue-router'
import App from './App.vue';
import Page from './components/Page.vue';
import Article from './components/Article.vue';
import Teaser from './components/Teaser.vue';
import Grid from './components/Grid.vue';
import Feature from './components/Feature.vue';
import PageView from './PageView.vue'
import ArticleView from './ArticleView.vue'
import ArticleOverview from './ArticleOverview.vue';

const routes = [
  { path: '/', component: PageView },
  { path: '/:slug', component: PageView },
  { path: '/articles/:slug', component: ArticleView },
  { path: '/article-overview', component: ArticleOverview },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
})

const app = createApp(App);

app.use(StoryblokVue, {
  accessToken: import.meta.env.VITE_STORYBLOK_DELIVERY_API_TOKEN,
  use: [apiPlugin],
});

app.component('Page', Page);
app.component('Article', Article);
app.component('Teaser', Teaser);
app.component('Grid', Grid);
app.component('Feature', Feature);
app
  .use(router)
  .mount('#app');

Now, the article overview page shows a list of links to all articles.

Create the article block

Add a new src/components/Article.vue component to render the new article content type.

src/components/Article.vue
<script setup>
defineProps({ blok: Object });
</script>

<template>
	<div v-editable="blok">
		<h1>{{ blok.title }}</h1>
		<StoryblokRichText :doc="blok.content" />
	</div>
</template>

To render rich text fields, the StoryblokRichText component provided by the @storyblok/vue module is used.

Learn more about handling rich text in Storyblok in the fields concept and the @storyblok/richtext package reference.

Create an ArticleView.vue file to render this content type.

src/ArticleView.vue
<script setup>
import { useStoryblok } from '@storyblok/vue';
import { useRoute } from 'vue-router'

const slug = useRoute().params.slug

const { data } = await useStoryblok(`articles/${slug}`, {
  version: 'draft',
});
const { story } = data;
</script>

<template>
  <StoryblokComponent v-if="story" :blok="story.content" />
</template>

Update your main file to handle the new routes.

src/main.js
import { createApp } from 'vue';
import { StoryblokVue, apiPlugin } from '@storyblok/vue';
import { createWebHistory, createRouter } from 'vue-router'
import App from './App.vue';
import Page from './components/Page.vue';
import Article from './components/Article.vue';
import Teaser from './components/Teaser.vue';
import Grid from './components/Grid.vue';
import Feature from './components/Feature.vue';
import PageView from './PageView.vue'
import ArticleView from './ArticleView.vue'

const routes = [
  { path: '/', component: PageView },
  { path: '/:slug', component: PageView },
  { path: '/articles/:slug', component: ArticleView },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
})

const app = createApp(App);

app.use(StoryblokVue, {
  accessToken: import.meta.env.VITE_STORYBLOK_DELIVERY_API_TOKEN,
  use: [apiPlugin],
});

app.component('Page', Page);
app.component('Article', Article);
app.component('Teaser', Teaser);
app.component('Grid', Grid);
app.component('Feature', Feature);
app
  .use(router)
  .mount('#app');

When clicking on links present in the article overview page, an article page renders correctly.

Handle referenced stories

In the PageView.vue file, set the resolve_relations parameter to get the full object response of referenced stories.

src/PageView.vue
<script setup>
import { useStoryblokApi, useStoryblokBridge } from '@storyblok/vue';
import { useRoute } from 'vue-router'

const slug = useRoute().params.slug

const storyblokApi = useStoryblokApi();
const { data } = await storyblokApi.get(`cdn/stories/${slug ? slug : 'home'}`, {
  version: 'draft',
  resolve_relations: "featured-articles.articles"
});
const { story } = data;
useStoryblokBridge(story.id)
</script>

<template>
  <StoryblokComponent v-if="story" :blok="story.content" />
</template>

Learn more in the references concept documentation.

Next, create a new src/components/FeaturedArticles.vue component.

src/components/FeaturedArticles.vue
<script setup>
defineProps({ blok: Object});
</script>

<template>
  <section v-editable="blok">
		<h2>Featured Articles</h2>
		<ul>
			<li v-for="article in blok.featured_articles" :key="article.uuid">
				<RouterLink :to="article.full_slug">
					{{ article.content.title }}
				</RouterLink>
			</li>
		</ul>
  </section>
</template>
src/main.js
import { createApp } from 'vue';
import { StoryblokVue, apiPlugin } from '@storyblok/vue';
import { createWebHistory, createRouter } from 'vue-router'
import App from './App.vue';
import Page from './components/Page.vue';
import Article from './components/Article.vue';
import Teaser from './components/Teaser.vue';
import Grid from './components/Grid.vue';
import Feature from './components/Feature.vue';
import FeaturedArticles from './components/FeaturedArticles.vue';
import PageView from './PageView.vue'
import ArticleView from './ArticleView.vue'

const routes = [
  { path: '/', component: PageView },
  { path: '/:slug', component: PageView },
  { path: '/articles/:slug', component: ArticleView },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
})

const app = createApp(App);

app.use(StoryblokVue, {
  accessToken: import.meta.env.VITE_STORYBLOK_DELIVERY_API_TOKEN,
  use: [apiPlugin],
});

app.component('Page', Page);
app.component('Article', Article);
app.component('Teaser', Teaser);
app.component('Grid', Grid);
app.component('Feature', Feature);
app.component('FeaturedArticles', FeaturedArticles);
app
  .use(router)
  .mount('#app');

Now, this component will render links to the featured articles in the home page of your project.