SR
Click to open github profile
Usage

Connect Components

Learn how to integrate Craft CMS data with your Vue components.


This guide explains the steps to connect your Vue components with data from Craft CMS. We’ll create a custom fetch function, mapping object for components, query data, and use the <CraftPage/> and <CraftArea/> components to display content dynamically.

Custom Fetch Function

Before we can show data we should build a custom fetch function. For that you can make a new util.

~/utils/fetcher.ts
export async function fetchData(url: string) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Failed to fetch data from ${url}: ${response.statusText}`);
  }

  return await response.json();
}

Catch all route

To support dynamic URLs in your Vue app, add a catch-all route by following this guide. Your router.ts file can look like that:

router.ts
import { createRouter, createWebHistory } from 'vue-router';
import CraftRouter from './CraftRouter.vue'; // next step

export const router = createRouter({
  history: createWebHistory(),
  routes: [{ path: '/:pathMatch(.*)*', component: CraftRouter }],
});

CraftRouter Component

To handle the routing correctly you should now make a CraftRouter.vue. This file is there to handle the routing Logic based on the router uri.

We watch if the uri changes and if so we update the uri variable. This is neccessary for the correct fetch to Craft CMS.

CraftRouter.vue
<script setup lang="ts">
  import { ref, watch } from 'vue';
  import { useRoute } from 'vue-router';

  const route = useRoute();
  const uri = ref(route.params.pathMatch || '__home__');

  watch(
    () => route.fullPath,
    async () => {
      uri.value = route.params.pathMatch || '__home__';
    },
  );
</script>

<template>
  <div>
    {{ uri }}
  </div>
</template>

Mapping Object

Define a mapping object in the CraftRouter.vue file. This object connects each Craft CMS section handle to a Vue page component and each field handle to a specific Vue component. This setup allows the correct component to render based on the Craft CMS data.

For example:

CraftRouter.vue
<script setup lang="ts">
  import { ref, watch } from 'vue';
  import { useRoute } from 'vue-router';

  // some dummy components 
  import Home from './views/home.vue';
  import News from './views/news.vue';
  import Headline from './components/headline.vue';

  // that's how you connect Craft with Vue
  const mapping: Config = {
    pages: {
      home: Home,
      news: News,
    },
    components: {
      headline: Headline,
      imageText: CraftNotImplemented,
    },
  };

  const route = useRoute();
  const uri = ref(route.params.pathMatch || '__home__');

  watch(
    () => route.fullPath,
    async () => {
      uri.value = route.params.pathMatch || '__home__';
    },
  );
</script>

<template>
  <div>
    {{ uri }}
  </div>
</template>

Working with Entry Types

When enableEntryTypeMapping is set to true in your configuration in your main.ts, you can link your Craft entries using the format sectionHandle:entryTypeHandle.

If the entry type handle is default or matches the section handle, you don’t need to explicitly define the :entryTypeHandle.

const mapping: Config = {
  pages: {
    home: Home, // equivalent to home:default or home:home
    'news:default': News, // equivalent to news or news:news
    'news:reference': News, // section handle = news, entry type handle = reference
  },
  components: {
    // additional components
  },
};

Display Page

Use the fetchData() helper to retrieve data from Craft CMS, and display the page content using the <CraftPage/> component within CraftRouter.vue. This component automatically renders the defined pages based on the mapping configuration and the data fetched from Craft CMS.

Tip

To find out more about the <CraftPage/> check out the craft page docs.

Here’s how your CraftRouter.vue looks now.

CraftRouter.vue
<script setup lang="ts">
  import { ref, watch } from 'vue';
  import { useRoute } from 'vue-router';
  import { useCraftUrlBuilder, CraftNotImplemented, CraftPage, type Config } from 'vue-craftcms';
  import { fetchData } from './utils/fetcher';

  import Home from './views/home.vue';
  import News from './views/news.vue';
  import Headline from './components/headline.vue';

  const mapping: Config = {
    pages: {
      home: Home,
      news: News,
    },
    components: {
      headline: Headline,
      imageText: CraftNotImplemented,
    },
  };

  const route = useRoute();
  const uri = ref(route.params.pathMatch || '__home__');
  const urlBuilder = useCraftUrlBuilder('entries'); // init here to use it in the right Vue context
  const data = ref(await fetchData(urlBuilder.uri(uri.value).buildUrl('one'))); // on initial load

  watch(
    () => route.fullPath,
    async () => {
      uri.value = route.params.pathMatch || '__home__';
      data.value = await fetchData(urlBuilder.uri(uri.value).buildUrl('one')); // on route change
    },
  );
</script>

<template>
  <div>
    <!-- Let the CraftPage handle the rest -->
    <CraftPage :config="mapping" :content="data" />
  </div>
</template>

This setup should render the correct Vue page based on your defined mapping object.

To verify the data structure Craft CMS sends to your page, you can add the following code to inspect the data in ./views/home.vue:

./views/home.vue
<template>
  <h1>Home</h1>
  <pre>
    {{ $attrs }}
  </pre>
</template>

Display Components

To connect Matrix blocks with Vue components, use the <CraftArea/> component. This component will dynamically render Vue components based on the content provided from Craft CMS.

Example:

./views/home.vue
<script setup lang="ts">
  const props = defineProps({
    metadata: {
      type: Object,
      required: true,
    },
    contentBuilder: {
      type: Object,
      required: true,
    },
    sectionHandle: {
      type: String,
      required: true,
    },
    title: {
      type: String,
      required: true,
    },
  });
</script>

<template>
  <div>
    <h1>{{ props.title }}</h1>
    <CraftArea v-if="props.contentBuilder" :content="props.contentBuilder" />
  </div>
</template>
Tip

To find out more about the <CraftArea/> check out the docs.


Copyright © 2024 Samuel Reichör