frontmatter-markdown-loader: "[WARN] Cannot stringify a function render" after upgrade to 3.x
I’ve been building a static blog with Nuxt.js, and I’m using frontmatter-markdown-loader with [Mode.VUE_RENDER_FUNCTIONS] to render Markdown pages and posts which contain Vue components. This was working great on v2.3.0, but after upgrading to v3.1.0, I cannot properly render Markdown files which are loaded dynamically using Nuxt’s asyncData function.
Here’s my dynamic render component:
<!-- components/DynamicMarkdown.vue -->
<script>
export default {
props: {
renderFn: {
type: Function,
required: true,
},
staticRenderFns: {
type: Array,
required: true,
},
},
data() {
return {
templateRender: null,
};
},
created() {
this.templateRender = this.renderFn;
this.$options.staticRenderFns = this.staticRenderFns;
},
render(createElement) {
return this.templateRender ? this.templateRender() : createElement('div', 'Rendering...');
},
};
</script>
And here is the page component for individual blog posts:
<!-- pages/blog/_slug.vue -->
<template>
<DynamicMarkdown
:render-fn="renderFn"
:static-render-fns="staticRenderFns"
/>
</template>
<script>
import DynamicMarkdown from '~/components/DynamicMarkdown.vue';
export default {
components: {
DynamicMarkdown,
},
async asyncData({ params }) {
const article = await import(`~/content/articles/${params.slug}.md`);
return {
renderFn: article.vue.render,
staticRenderFns: article.vue.staticRenderFns,
};
},
};
</script>
This works if I link to a blog post from somewhere else in the app (ie, the post is rendered client-side). However, if I reload the page, or if I visit the permalink directly, the page crashes and I see several errors. In the browser console, I get TypeError: this.templateRender is not a function.

And in the terminal I see two warnings: WARN Cannot stringify a function render and WARN Cannot stringify a function.

With FML version 2.3.0, this approach worked fine, both with client-side and server-side rendering.
Another releveant bit of information is that if I first load a Markdown file at the top of my <script> section using regular ES6 module syntax, everything works fine.
The following code allows the page to be loaded either client-side or server-side:
<!-- pages/blog/_slug.vue -->
<template>
<DynamicMarkdown
:render-fn="renderFn"
:static-render-fns="staticRenderFns"
/>
</template>
<script>
import DynamicMarkdown from '~/components/DynamicMarkdown.vue';
import article from '~/content/articles/2019-10-14-my-post.md';
export default {
components: {
DynamicMarkdown,
},
data() {
return {
fm: null,
renderFn: null,
staticRenderFns: null,
};
},
created() {
this.fm = article.attributes;
this.renderFn = article.vue.render;
this.staticRenderFns = article.vue.staticRenderFns;
},
};
</script>
Obviously, the previous code example is not practical since blog posts must be loaded dynamically by extracting the file name from params. Hence the need for asyncData imports.
In summary, if I import a Markdown file using ES6 module syntax, everything works. But if I import it inside asyncData it breaks.
If it helps to see a complete app that demonstrates this issue, please have a look at nuxt-markdown-blog-starter by @marinaaisa. I referenced her code a lot when building my own blog (thank you, @marinaaisa!), and after she recently upgraded FML to v3.0.0, her app manifests the exact problem I have described above.
I am aware that FML v3.0.0 introduced breaking changes, and as best I can tell the root issue is that vue.render and vue.staticRenderFns now return functions instead of strings. I’ve looked at your source code to try and find a workaround, but I’m afraid my understanding of Vue render functions is too rudimentary.
Thank you for all your work on frontmatter-markdown-loader. I really love this project since it enables Vue components to be embedded in Markdown files. This is a huge win for blogging, and I really hope a solution can be found to allow for asyncData file imports. I would appreciate any help or advice you can offer on this issue!
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 3
- Comments: 24 (10 by maintainers)
@lukeocodes Right. Unfortunately, Vue’s async component feature only handles component importing for SSR.
so,
doesn’t work for
attributesin the server-side. The below works for SSR instead, but lose benefits of async component 😞Nuxt team recently released “content module”, that may solve all markdown handling in Nuxt without this loader 😉 https://github.com/nuxt/content
Hmmm, I don’t think we need to persist in using
asyncDatabecauseasyncDatais designed for passing primitives which can be generated only in backend servers to use in clients. And typical FML origin components can work with both. can’t imagine the exact use-case we need to useasyncDatayet.beforeCreateandcreatedhooks are called in SSR process. (Try cURL your Nuxt server, you can see imported components on HTML before running JS in the frontend, Joshua) https://ssr.vuejs.org/guide/universal.html#component-lifecycle-hooks So, pre-rendering/SSR could work without usingasyncData.Eventually, I prefer to remove exporting
render/staticRenderFnsbecause that is exposing Vue component’s internal processing which end-user don’t need to care basically. I regret I exposed that way originally, but really encourage users to usevue.componentinstead 😉Even so, you love to use
asyncData, stick version of FML as 2.x or serialize imported function withJSON.stringify,toString()or whatever.@hmsk
This might be a lot to ask, however it would be amazing if we could actually load the vue component inside of nuxt asyncData. Loading the component in asyncData makes it load server side, which slightly increases the performance, but most importantly it allows the page to be loaded without javascript. Not quite sure how that could be implemented though, perhaps a custom serializer?
@joshukraine thanks for waiting for my late second reply 🙃 The code block is just pasted from my project (TypeScript + property-decorator), so that might confuse you. Below is the minimum code of SFC to render markdown and attributes.
Ref (The repo is the original use-case of frontmatter-markdown-loader before I founded): https://github.com/haiiro-io/violet/blob/master/components/Work/DynamicMarkdown.vue
@joshukraine , From your example, I think you’re using Nuxt Markdown Blog starter from @marinaaisa. I’ve created PR to solve this issue and it already merged as well, https://github.com/marinaaisa/nuxt-markdown-blog-starter/pull/9
You’re not using Async Components correctly. https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components
The original code seems to work for the client side in coincidence unfortunately 🙃
@hmsk
Aha, of course! That worked. 🙂 As far as I’m concerned, that resolves this issue, so I’ll go ahead and close.
Thanks again, and have an awesome day! 😎🙌🏻
@joshukraine
Haha, that might be. I didn’t run that code actually 🙈
I guess the title requires null-check since the attributes are provided asynchronously 🤔
And thanks for giving me coffees on https://www.buymeacoffee.com/hmsk 🙏 I really appreciate and am honored.
@hmsk Thank you so much for your reply and the example code! That helped tremendously. I’ve implemented your solution in my blog and for the most part it works as you demonstrate in your code sample. (FWIW, you have a couple of typos in that code.)
Anyway, the one thing that does not seem to work as intended is the frontmatter. The
attributesdata property loads fine and is visible in Vue devtools.But if I reference it anywhere in the view template, the whole page crashes with a TypeError:
Cannot read property 'title' of null.For my use case it’s actually not a big deal since I am loading the frontmatter in a different way. But I think for many people it would be an issue.
If it helps, my Nuxt blog is now complete and I’ve open sourced the repo. The FML 3.x upgrade is in this PR: https://github.com/joshukraine/ofreport.com/pull/44
Thank you again for your help! 😃
Of course. Here’s the
applyHighlightmethod I made:nodeis the resultingVNode,his the function to create them. I then justWhere would I search for the highlighted text with
Mode.VUE_COMPONENT?md.vue.component.$el?btw component mode might be easier to use. Here’s my recent dynamic-markdown-ish implement with Nuxt. (And I’m planning to remove
render/staticRenderFnsat all sincecomponentmay cover every cases)That seems not this loader’s problem.
Since
asyncDatain Nuxt doesn’t expect function in a returning object. On SSR process (so when reloading the page as you mentioned), Nuxt does some converting for the returned object. Then that shows such warnings.Not sure why
nuxt-markdown-blog-starterusesasyncDatafor markdown content functions. Because the imported contents are rendered in under<client-only>🤔 Why don’t you usecreated ()?I’d like to think about supporting “stringify-ed exports” as well as previous versions if there is a reasonable use-case, but I’m not convinced by the approach of
nuxt-markdown-blog-starter.