A set of components and utilities to work faster with DatoCMS in Vue.js environments. Integrates seamlessy with DatoCMS's GraphQL Content Delivery API.
- Compatible with any data-fetching library (axios, Apollo);
- Usable both client and server side;
- Compatible with vanilla Vue, Nuxt.js and pretty much any other Vue-based solution;
- Demos
- Installation
- Progressive/responsive image
- Social share, SEO and Favicon meta tags
- Structured text
- Pure Vue: https://vue-datocms-example.netlify.com/
- Server side rendering with Nuxt.js: https://vue-datocms-components.now.sh
npm install vue-datocms
<datocms-image>
is a Vue component specially designed to work seamlessly with DatoCMS’s responsiveImage
GraphQL query that optimizes image loading for your sites.
- Offer WebP version of images for browsers that support the format
- Generate multiple smaller images so smartphones and tablets don’t download desktop-sized images
- Efficiently lazy load images to speed initial page load and save bandwidth
- Use either blur-up or background color techniques to show a preview of the image while it loads
- Hold the image position so your page doesn’t jump while images load
You can register the component globally so it's available in all your apps:
import Vue from "vue";
import { DatocmsImagePlugin } from "vue-datocms";
Vue.use(DatocmsImagePlugin);
Or use it locally in any of your components:
import { Image } from "vue-datocms";
export default {
components: {
"datocms-image": Image,
},
};
- Use
<datocms-image>
it in place of the regular<img />
tag - Write a GraphQL query to your DatoCMS project using the
responsiveImage
query
The GraphQL query returns multiple thumbnails with optimized compression. The <datocms-image>
component automatically sets up the "blur-up" effect as well as lazy loading of images further down the screen.
For a fully working example take a look at our examples directory.
<template>
<article>
<div v-if="data">
<h1>{{ data.blogPost.title }}</h1>
<datocms-image :data="data.blogPost.cover.responsiveImage" />
</div>
</article>
</template>
<script>
import { request } from "./lib/datocms";
import { Image } from "vue-datocms";
const query = gql`
query {
blogPost {
title
cover {
responsiveImage(
imgixParams: { fit: crop, w: 300, h: 300, auto: format }
) {
# HTML5 src/srcset/sizes attributes
srcSet
webpSrcSet
sizes
src
# size information (post-transformations)
width
height
aspectRatio
# SEO attributes
alt
title
# background color placeholder or...
bgColor
# blur-up placeholder, JPEG format, base64-encoded
base64
}
}
}
}
`;
export default {
components: {
"datocms-image": Image,
},
data() {
return {
data: null,
};
},
async mounted() {
this.data = await request({ query });
},
};
</script>
prop | type | default | required | description |
---|---|---|---|---|
data | ResponsiveImage object |
✅ | The actual response you get from a DatoCMS responsiveImage GraphQL query. |
|
class | string | null | ❌ | Additional CSS class of root node |
root-style | CSS properties | null | ❌ | Additional CSS rules to add to the root node |
picture-class | string | null | ❌ | Additional CSS class for the inner <picture /> tag |
picture-style | CSS properties | null | ❌ | Additional CSS rules to add to the inner <picture /> tag |
fade-in-duration | integer | 500 | ❌ | Duration (in ms) of the fade-in transition effect upoad image loading |
intersection-treshold | float | 0 | ❌ | Indicate at what percentage of the placeholder visibility the loading of the image should be triggered. A value of 0 means that as soon as even one pixel is visible, the callback will be run. A value of 1.0 means that the threshold isn't considered passed until every pixel is visible. |
intersection-tmargin | string | "0px 0px 0px 0px" | ❌ | Margin around the placeholder. Can have values similar to the CSS margin property (top, right, bottom, left). The values can be percentages. This set of values serves to grow or shrink each side of the placeholder element's bounding box before computing intersections. |
lazy-load | Boolean | true | ❌ | Wheter enable lazy loading or not |
explicitWidth | Boolean | false | ❌ | Wheter the image wrapper should explicitely declare the width of the image or keep it fluid |
The data
prop expects an object with the same shape as the one returned by responsiveImage
GraphQL call. It's up to you to make a GraphQL query that will return the properties you need for a specific use of the <datocms-image>
component.
- The minimum required properties for
data
are:aspectRatio
,width
,sizes
,srcSet
andsrc
; alt
andtitle
, while not mandatory, are all highly suggested, so remember to use them!- You either want to add the
webpSrcSet
field or specify{ auto: format }
in yourimgixParams
, to automatically use WebP images in browsers that support the format; - If you provide both the
bgColor
andbase64
property, the latter will take precedence, so just avoiding querying both fields at the same time, it will only make the response bigger 😉
Here's a complete recap of what responsiveImage
offers:
property | type | required | description |
---|---|---|---|
aspectRatio | float | ✅ | The aspect ratio (width/height) of the image |
width | integer | ✅ | The width of the image |
sizes | string | ✅ | The HTML5 sizes attribute for the image |
srcSet | string | ✅ | The HTML5 srcSet attribute for the image |
src | string | ✅ | The fallback src attribute for the image |
webpSrcSet | string | ❌ | The HTML5 srcSet attribute for the image in WebP format, for browsers that support the format |
alt | string | ❌ | Alternate text (alt ) for the image |
title | string | ❌ | Title attribute (title ) for the image |
bgColor | string | ❌ | The background color for the image placeholder |
base64 | string | ❌ | A base64-encoded thumbnail to offer during image loading |
Just like the image component, toHead()
is a helper specially designed to work seamlessly with DatoCMS’s _seoMetaTags
and faviconMetaTags
GraphQL queries so that you can handle proper SEO in your pages.
You can use toHead()
inside the metaInfo
(or head
, in Nuxt.js) property of your components, and it will return meta tags as required by the vue-meta
package.
toHead()
takes an array of Tag
s in the exact form they're returned by the following DatoCMS GraphQL API queries:
_seoMetaTags
query on any record, orfaviconMetaTags
on the global_site
object.
You can pass multiple arrays of Tag
s together and pass them to a single toHead()
call.
For a working example take a look at our examples directory.
<template>
<article>
<h1 v-if="data">{{ data.page.title }}</h1>
</article>
</template>
<script>
import { request } from "./lib/datocms";
import { toHead } from "vue-datocms";
const query = gql`
query {
page: homepage {
title
seo: _seoMetaTags {
attributes
content
tag
}
}
site: _site {
favicon: faviconMetaTags {
attributes
content
tag
}
}
}
`;
export default {
data() {
return {
data: null,
};
},
async mounted() {
this.data = await request({ query });
},
metaInfo() {
if (!this || !this.data) {
return;
}
return toHead(this.data.page.seo, this.data.site.favicon);
},
};
</script>
<datocms-structured-text />
is a Vue component that you can use to render the value contained inside a DatoCMS Structured Text field type.
You can register the component globally so it's available in all your apps:
import Vue from "vue";
import { DatocmsStructuredTextPlugin } from "vue-datocms";
Vue.use(DatocmsStructuredTextPlugin);
Or use it locally in any of your components:
import { StructuredText } from "vue-datocms";
export default {
components: {
"datocms-structured-text": StructuredText,
},
};
<template>
<article>
<div v-if="data">
<h1>{{ data.blogPost.title }}</h1>
<datocms-structured-text :data="{data.blogPost.content}" />
<!--
Final result:
<h1>Hello <strong>world!</strong></h1>
-->
</div>
</article>
</template>
<script>
import { request } from "./lib/datocms";
import { StructuredText } from "vue-datocms";
const query = gql`
query {
blogPost {
title
content {
value
}
}
}
`;
export default {
components: {
"datocms-structured-text": StructuredText,
},
data() {
return {
data: null,
};
},
async mounted() {
this.data = await request({ query });
// data.blogPost.content ->
// {
// value: {
// schema: "dast",
// document: {
// type: "root",
// children: [
// {
// type: "heading",
// level: 1,
// children: [
// {
// type: "span",
// value: "Hello ",
// },
// {
// type: "span",
// marks: ["strong"],
// value: "world!",
// },
// ],
// },
// ],
// },
// },
// }
},
};
</script>
You can also pass custom renderers for special nodes (inline records, record links and blocks) as an optional parameter like so:
<template>
<article>
<div v-if="data">
<h1>{{ data.blogPost.title }}</h1>
<datocms-structured-text
:data="data.blogPost.content"
:renderInlineRecord="renderInlineRecord"
:renderLinkToRecord="renderLinkToRecord"
:renderBlock="renderBlock"
/>
<!--
Final result:
<h1>Welcome onboard <a href="/team/mark-smith">Mark</a></h1>
<p>
So happy to have
<a href="/team/mark-smith">this awesome humang being</a> in our team!
</p>
<img
src="https://www.datocms-assets.com/205/1597757278-austin-distel-wd1lrb9oeeo-unsplash.jpg"
alt="Our team at work"
/>
-->
</div>
</article>
</template>
<script>
import { request } from "./lib/datocms";
import { StructuredText } from "vue-datocms";
const query = gql`
query {
blogPost {
title
content {
value
}
}
}
`;
export default {
components: {
"datocms-structured-text": StructuredText,
},
data() {
return {
data: null,
};
},
methods: {
renderInlineRecord: ({ record, key, h }) => {
switch (record.__typename) {
case "TeamMemberRecord":
return h(
"a",
{ key, attrs: { href: `/team/${record.slug}` } },
record.firstName,
);
default:
return null;
}
},
renderLinkToRecord: ({ record, children, key, h }) => {
switch (record.__typename) {
case "TeamMemberRecord":
return h(
"a",
{ key, attrs: { href: `/team/${record.slug}` } },
children,
);
default:
return null;
}
},
renderBlock: ({ record, key, h }) => {
switch (record.__typename) {
case "ImageRecord":
return h("img", {
key,
attrs: { src: record.image.url, alt: record.image.alt },
});
default:
return null;
}
},
},
async mounted() {
this.data = await request({ query });
// data.blogPost.content ->
// {
// value: {
// schema: "dast",
// document: {
// type: "root",
// children: [
// {
// type: "heading",
// level: 1,
// children: [
// { type: "span", value: "Welcome onboard " },
// { type: "inlineItem", item: "324321" },
// ],
// },
// {
// type: "paragraph",
// children: [
// { type: "span", value: "So happy to have " },
// {
// type: "itemLink",
// item: "324321",
// children: [
// {
// type: "span",
// marks: ["strong"],
// value: "this awesome humang being",
// },
// ]
// },
// { type: "span", value: " in our team!" },
// ]
// },
// { type: "block", item: "1984559" }
// ],
// },
// },
// links: [
// {
// id: "324321",
// __typename: "TeamMemberRecord",
// firstName: "Mark",
// slug: "mark-smith",
// },
// ],
// blocks: [
// {
// id: "324321",
// __typename: "ImageRecord",
// image: {
// alt: "Our team at work",
// url: "https://www.datocms-assets.com/205/1597757278-austin-distel-wd1lrb9oeeo-unsplash.jpg",
// },
// },
// ],
// }
},
};
</script>
prop | type | required | description | default |
---|---|---|---|---|
data | StructuredTextGraphQlResponse | DastNode |
✅ | The actual field value you get from DatoCMS | |
renderInlineRecord | ({ record }) => VNode | null |
Only required if document contains inlineItem nodes |
Convert an inlineItem DAST node into React |
[] |
renderLinkToRecord | ({ record, children }) => VNode | null |
Only required if document contains itemLink nodes |
Convert an itemLink DAST node into React |
null |
renderBlock | ({ record }) => VNode | null |
Only required if document contains block nodes |
Convert a block DAST node into React |
null |
customRules | Array<RenderRule> |
❌ | Customize how document is converted in JSX (use renderRule() to generate) |
null |
renderText | (text: string, key: string) => VNode | string | null |
❌ | Convert a simple string text into React | (text) => text |