zce / velite

Turns Markdown / MDX, YAML, JSON, or others into app's data layer with Zod schema.
http://velite.js.org
MIT License
476 stars 23 forks source link

I can display the main content - SyntaxError: Unexpected token '<' #97

Closed johanguse closed 6 months ago

johanguse commented 7 months ago

Hey guys! I moved from contentlayer and everything seems to be working well except for one thing, I'm unable to display the main content (body/content) of my MDX file. I've used the MDXContent compoment from the demo

Do you have any thoughts on how to tackle this issue?

 ○ Compiling /blog/[...slug] ...
 ✓ Compiled /blog/[...slug] in 503ms
 ⨯ src/components/content/mdx-content.tsx (11:12) @ useMDXComponent
 ⨯ SyntaxError: Unexpected token '<'
    at new Function (<anonymous>)
    at useMDXComponent (/saas-template/.next/server/chunks/[root of the server]__f067be._.js:2176:16)
    at MDXContent (/saas-template/.next/server/chunks/[root of the server]__f067be._.js:2182:23)
    at stringify (<anonymous>)
   9 |
  10 | const useMDXComponent = (code: string) => {
> 11 |   const fn = new Function(code)
     |            ^
  12 |   return fn({ ...runtime }).default
  13 | }
  14 |

next.config.js

import('./env.mjs')

/** @type {import('next').NextConfig} */
module.exports = {
  reactStrictMode: true,
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'avatars.githubusercontent.com',
      },
      {
        protocol: 'https',
        hostname: 'lh3.googleusercontent.com',
      },
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
      },
      {
        protocol: 'https',
        hostname: 'source.unsplash.com',
      },
      {
        protocol: 'https',
        hostname: 'i.pravatar.cc',
      },
      {
        protocol: 'https',
        hostname: 'res.cloudinary.com',
      },
    ],
  },
  transpilePackages: ['html-to-text'],
  experimental: {
    serverComponentsExternalPackages: ['@prisma/client'],
  },
  webpack: (config) => {
    config.plugins.push(new VeliteWebpackPlugin())
    return config
  },
}

class VeliteWebpackPlugin {
  static started = false
  apply(/** @type {import('webpack').Compiler} */ compiler) {
    // executed three times in nextjs
    // twice for the server (nodejs / edge runtime) and once for the client
    compiler.hooks.beforeCompile.tapPromise('VeliteWebpackPlugin', async () => {
      if (VeliteWebpackPlugin.started) return
      VeliteWebpackPlugin.started = true
      const dev = compiler.options.mode === 'development'
      const { build } = await import('velite')
      await build({ watch: dev, clean: !dev })
    })
  }
}

mdx-content.tsx

import * as runtime from 'react/jsx-runtime'

import Image from 'next/image'

interface MdxProps {
  code: string
  components?: Record<string, React.ComponentType>
}

const useMDXComponent = (code: string) => {
  const fn = new Function(code)
  return fn({ ...runtime }).default
}

export function MDXContent({ code, components }: MdxProps) {
  const Component = useMDXComponent(code)
  return <Component components={{ Image, ...components }} />
}

velite.config.js

import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypePrettyCode from 'rehype-pretty-code'
import rehypeSlug from 'rehype-slug'
import { defineCollection, defineConfig, s } from 'velite'

const allAuthors = defineCollection({
  name: 'Author',
  pattern: 'authors/**/*.mdx',
  schema: s
    .object({
      title: s.string(),
      description: s.string().optional(),
      avatar: s.string().optional(),
      twitter: s.string().optional(),
    })
    .transform((data) => ({
      ...data,
      slug: data.title
        .toLowerCase()
        .replace(/ /g, '-')
        .replace(/[^\w-]+/g, ''),
    })),
})

const allDocs = defineCollection({
  name: 'Doc',
  pattern: 'docs/**/*.mdx',
  schema: s.object({
    slug: s.path(),
    title: s.string(),
    description: s.string(),
    body: s.markdown(),
    published: s.boolean().default(true),
  }),
})

const allGuides = defineCollection({
  name: 'Guide',
  pattern: 'guides/**/*.mdx',
  schema: s.object({
    slug: s.path(),
    title: s.string(),
    description: s.string(),
    body: s.markdown(),
    date: s.isodate(),
    published: s.boolean().default(true),
    featured: s.boolean().default(false),
  }),
})

const allPages = defineCollection({
  name: 'Page',
  pattern: 'pages/**/*.mdx',
  schema: s.object({
    slug: s.path(),
    title: s.string(),
    description: s.string(),
    body: s.markdown(),
  }),
})

const allPosts = defineCollection({
  name: 'Post',
  pattern: 'blog/**/*.mdx',
  schema: s
    .object({
      slug: s.path(),
      title: s.string(),
      description: s.string(),
      body: s.markdown(),
      date: s.isodate(),
      published: s.boolean().default(true),
      image: s.string().optional(),
      authors: s.array(s.string()),
      tags: s.array(s.string()).optional(),
      category: s.array(s.string()).optional(),
      hasToc: s.boolean().default(false),
      toc: s.toc(),
    })
    .transform((data) => ({
      ...data,
      slug: data.slug.replace('blog/', ''),
    })),
})

export default defineConfig({
  root: 'content',
  output: {
    data: '.velite',
    assets: 'public/static',
    base: '/static/',
    name: '[name]-[hash:6].[ext]',
    clean: true,
  },
  collections: {
    allAuthors,
    allDocs,
    allGuides,
    allPages,
    allPosts,
  },
  mdx: {
    rehypePlugins: [
      rehypeSlug,
      [rehypePrettyCode, { theme: 'github-dark' }],
      [
        rehypeAutolinkHeadings,
        {
          behavior: 'wrap',
          properties: {
            className: ['subheading-anchor'],
            ariaLabel: 'Link to section',
          },
        },
      ],
    ],
    remarkPlugins: [],
  },
})

allPosts.jon

[
  {
    "slug": "10-things-you-most-likely-didnt-know-about-health",
    "title": "10 Things You Most Likely Didn't Know About Health.",
    "description": "Culpa laboris aliquip ea consectetur mollit ea ipsum sint qui culpa laboris dolor adipisicing proident. Et officia consequat do nulla tempor cupidatat elit.",
    "body": "<p>Cupidatat voluptate deserunt non ea exercitation sit consequat ullamco ex nostrud elit magna. Nulla id proident labore pariatur pariatur ex ut ad enim et labore. Est do minim eiusmod culpa. Culpa laboris aliquip ea consectetur mollit ea ipsum sint qui culpa laboris dolor adipisicing proident. Et officia consequat do nulla tempor cupidatat elit. Consequat proident magna dolor labore et esse aute dolor sit ea.</p>\n<p>Sint amet deserunt commodo aute consectetur Lorem qui aliqua tempor nulla. Velit non ea qui aliquip. Qui laborum labore excepteur duis velit velit enim enim veniam. Pariatur laboris commodo est laboris. In incididunt pariatur aliquip ut elit irure magna anim sunt. Anim labore ut sit magna ex proident dolor anim cupidatat adipisicing.</p>\n<h2>5 Things</h2>\n<ul>\n<li>Minim est mollit commodo ad sit quis.</li>\n<li>Tempor id magna eu veniam sint et mollit magna laboris reprehenderit Lorem proident.</li>\n<li>Laborum id sint ex magna nulla est labore non.</li>\n<li>Pariatur qui qui ex duis nulla et aute magna incididunt cupidatat commodo.</li>\n<li>Sint amet deserunt commodo aute consectetur.</li>\n</ul>\n<p>Aliquip veniam aliquip nisi non amet pariatur quis. Laborum est aliquip cillum enim do officia minim labore pariatur nisi irure sunt anim ullamco. Mollit ullamco sint qui enim non Lorem aliquip nulla sint. Aute laborum tempor adipisicing officia magna fugiat sint cupidatat. Enim Lorem officia anim cillum ea tempor dolore voluptate.</p>\n<p>Consequat occaecat excepteur esse eu et ex officia adipisicing laborum qui duis tempor. Amet do pariatur elit aute fugiat eu ad. Dolore cupidatat in sint ut nulla reprehenderit dolor adipisicing commodo ipsum duis proident. Lorem anim veniam id aliqua.</p>\n<p>Excepteur aliqua minim Lorem officia ullamco pariatur. Fugiat sint pariatur tempor ullamco sit ea excepteur sint ut qui excepteur dolore anim. Eiusmod id dolor sit fugiat eu eiusmod tempor proident. Officia enim dolore excepteur proident incididunt et sint. Non laboris veniam nisi adipisicing magna.</p>\n<p>Nulla ut irure aliqua ex aliquip nisi non amet excepteur ipsum laboris voluptate elit. Duis cupidatat mollit ea ipsum tempor consectetur. Aliquip proident magna Lorem amet esse laborum cillum. Culpa aute laborum velit velit in do. Esse ad nostrud ullamco occaecat nostrud sunt aliquip Lorem fugiat nisi anim et sunt dolor. Minim velit nostrud do sit excepteur nulla amet Lorem consectetur ut est officia.</p>\n<p>Nisi ad aliquip minim quis cupidatat eu minim voluptate tempor consequat irure eu. Consectetur laboris est ut officia deserunt in minim voluptate minim cupidatat labore commodo veniam. Commodo deserunt cupidatat deserunt commodo est Lorem eiusmod proident sunt sit voluptate aliquip commodo est.</p>\n<p>Consectetur aliqua eu veniam consequat eu adipisicing id ullamco incididunt. Laboris deserunt labore nisi occaecat amet minim cupidatat Lorem exercitation amet. Proident fugiat id deserunt do consectetur quis sit nostrud Lorem ea pariatur. Occaecat et esse sunt dolore nisi aliquip et non do sint. Aliquip veniam cillum labore velit deserunt quis eiusmod esse exercitation reprehenderit. Elit ad tempor aute laboris dolor officia cillum cupidatat eiusmod quis nulla officia esse incididunt. Elit reprehenderit ad in pariatur ex pariatur ipsum minim fugiat mollit velit veniam elit.</p>\n<p>Qui duis excepteur culpa officia Lorem aliquip duis nisi id. Consequat ut eiusmod sit eiusmod. Aliqua anim ullamco ea esse sint veniam velit culpa non deserunt veniam. Eu elit amet reprehenderit ipsum eu ex do ullamco.</p>",
    "date": "2023-12-07T00:00:00.000Z",
    "published": true,
    "image": "/images/blog/vegetables.jpg",
    "authors": [
      "Curtis Lopez"
    ],
    "tags": [
      "Health",
      "Likely",
      "Didn't Know"
    ],
    "category": [
      "Health"
    ],
    "hasToc": false,
    "toc": [
      {
        "title": "5 Things",
        "url": "#5-things",
        "items": []
      }
    ]
  },
...
]
johanguse commented 7 months ago

This snippet of MDX also didn't work https://velite.js.org/other/snippets#built-in-mdx-compiler-result-render

johanguse commented 7 months ago

On my file page.tsx I've use like this <MDXContent code={post.body} />

johanguse commented 7 months ago

Move the BODY to mdx body: s.mdx(),

Error: Expected component `Callout` to be defined: you likely forgot to import, pass, or provide it.
    at _missingMdxReference (eval at useMDXComponent (/saas-template/.next/server/chunks/[root of the server]__f59136._.js:2229:16), <anonymous>:3:21808)
    at _createMdxContent (eval at useMDXComponent (/saas-template/.next/server/chunks/[root of the server]__f59136._.js:2229:16), <anonymous>:3:359)
    at default (eval at useMDXComponent (/saas-template/.next/server/chunks/[root of the server]__f59136._.js:2229:16), <anonymous>:3:21744)
    at stringify (<anonymous>)
zce commented 7 months ago

Distinguish between s.markdown() and s.mdx()

https://velite.js.org/guide/velite-schemas#s-mdx-options

zce commented 7 months ago

Move the BODY to mdx body: s.mdx(),

Error: Expected component `Callout` to be defined: you likely forgot to import, pass, or provide it.
    at _missingMdxReference (eval at useMDXComponent (/saas-template/.next/server/chunks/[root of the server]__f59136._.js:2229:16), <anonymous>:3:21808)
    at _createMdxContent (eval at useMDXComponent (/saas-template/.next/server/chunks/[root of the server]__f59136._.js:2229:16), <anonymous>:3:359)
    at default (eval at useMDXComponent (/saas-template/.next/server/chunks/[root of the server]__f59136._.js:2229:16), <anonymous>:3:21744)
    at stringify (<anonymous>)

You need to register the Callout component correctly like the Image component, Velite's built-in mdx schema does not bundle the local components.

zce commented 7 months ago

I have improved the document on using MDX for your reference: https://velite.js.org/guide/using-mdx

johanguse commented 6 months ago

Works, thank you!