The Blog of Rizal Renaldi

Rizal Renaldi

← Back

Nuxt Content image inside markdown

12 May 2021

code

What to achieve:
Displaying image from image that being called inside Nuxt Content's markdown

Problem: As of right now, at least when I wrote and try this in May 2021, calling image from markdow on Nuxt Content is impossible. There is an extensive discussion on Nuxt's github about this issues. I've tried everything from calling image using img tag, and moving around the images folder to different places. Obviously, as stated on Nuxt Content docs, Nuxt Content can not call any assets from /assets folder since its seperated from webpack. But I've tried to move the images to /static or within the /content folder, and none of these works.

One attempt that I thought was working was this. I create an image component and put it in global folder and directly call the component from inside the markdown file. The image is perfectly shows up, but then if there's any texts after the image, all those texts got cutted off.

Until I google "Nuxt content image" and found this codesandbox, which kind of sorcery. As any of my previous attempts ends up failed miserably, this one is scarily works.

To the one who made it, I'd like to say thank you so much. With none of companion article or explanation I tried to digging up those folders in codesandbox and try to understand how it works. So here's what I found.

It utilizing rehype plugin which I guess it's already installed out-of-the-box in Nuxt Content. So first create this rehype-content-image.js and put it on /plugins folder.

const visit = require("unist-util-visit");

module.exports = function nuxtContentImages() {
  // console.log(info)
  return function transformer(tree, file) {
    visit(tree, "element", visitor);

    function visitor(node) {
      if (node.tagName === "img") {
        node.tagName = "content-img";
        // console.log('rehype:', 'img -> content-img', node.properties.src)
      }
    }
  };
};

Next create a global component in /components/global for image component. I guess the logic in this component. Also judging by how long the codes are

<template>
  <img :src="source" :alt="alt" :title="title" />
</template>


<script>
const path = require("path");

const requireFromContent = (contentPath) => {
  let result;
  const ext = path.extname(contentPath);
  const name = path.basename(contentPath, ext);
  const dir = path.dirname(contentPath);
  // IMPORTANT
  // Every fixed string parts of the following require calls, like folder name, path separators
  // and file extensions, are crucial for narrowing down the required module path before the
  // variable part of the string is determined, see here:
  // https://webpack.js.org/guides/dependency-management/#require-with-expression
  switch (ext) {
    case ".svg":
      result = require(`~/content/${path.join(dir, name)}.svg`);
      break;
    case ".png":
      result = require(`~/content/${path.join(dir, name)}.png`);
      break;
    case ".jpg":
      result = require(`~/content/${path.join(dir, name)}.jpg`);
      break;
    case ".jpeg":
      result = require(`~/content/${path.join(dir, name)}.jpeg`);
      break;
    case ".gif":
      result = require(`~/content/${path.join(dir, name)}.gif`);
      break;
    default:
      result = null;
  }
  return result;
};

const requireFromAssets = (assetsPath) => {
  let result;
  const ext = path.extname(assetsPath);
  const name = path.basename(assetsPath, ext);
  const dir = path.dirname(assetsPath);
  // IMPORTANT
  // Every fixed string parts of the following require calls, like folder name, path separators
  // and file extensions, are crucial for narrowing down the required module path before the
  // variable part of the string is determined, see here:
  // https://webpack.js.org/guides/dependency-management/#require-with-expression
  switch (ext) {
    case ".svg":
      result = require(`/img/${path.join(dir, name)}.svg`);
      break;
    case ".png":
      result = require(`/img/${path.join(dir, name)}.png`);
      break;
    case ".jpg":
      result = require(`/img/${path.join(dir, name)}.jpg`);
      break;
    case ".jpeg":
      result = require(`/img/${path.join(dir, name)}.jpeg`);
      break;
    case ".gif":
      result = require(`/img/${path.join(dir, name)}.gif`);
      break;
    default:
      result = null;
  }
  return result;
};

export default {
  name: "ContentImg",
  props: {
    src: { type: String, required: true },
    alt: { type: String, default: undefined },
    title: { type: String, default: undefined },
  },
  computed: {
    source() {
      // './' is meant be used in '.md' files to reference image files located in same the
      // folder with the '.md' file.

      if (this.src.startsWith("./")) {
        // document.dir is available if inside nuxt-contet: <nuxt-content :document="document"></nuxt-content>
        // console.log(this.$parent.document)
        if (this.$parent.document && this.$parent.document.dir) {
          let dir = this.$parent.document.dir;
          if (dir.startsWith(path.sep)) dir = dir.replace(path.sep, "");
          return requireFromContent(path.join(dir, this.src));
        } else {
          throw new Error(`'./' should be used only in '.md' files!`);
        }
      } else if (
        this.src.startsWith("~/content/") ||
        this.src.startsWith("@/content/")
      ) {
        return requireFromContent(this.src.slice(10));
      } else if (
        this.src.startsWith("/img/") ||
        this.src.startsWith("@/assets/")
      ) {
        return requireFromAssets(this.src.slice(9));
      } else {
        return this.src;
      }
    },
  },
};
</script>

<style></style>

Finally put the plugin created in nuxt.config.js

content: {
    markdown: {
      rehypePlugins: ["~/plugins/rehype-content-image.js"]
    }
  },

Calling the image from markdown. The example on codesandbox showing us a couple examples to call image from different folder location.

---
title: Post1
description: This is post1 page.
---

Current dir image import
![Logo](/img/logo.svg "Logo Text")

Assets import
![Logo](/img/logo1.svg "Logo Text")

Link to [home](/)

The filtering occur in line 3 using .where and $contains

The Blog of Rizal Renaldi

2024 © rizalrenaldi.com — Made with 🖖