Menus

Apply to Frontend

The content you create can be published in any environment — such as Next, Nuxt, React, Vue, or Angular — using mm-renderer.

Install mm-renderer and @supabase/supabase-js

npm i mm-renderer @supabase/supabase-js

Next

// app/[path]/page.tsx

import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { createClient } from '@supabase/supabase-js'
import { generateCss, generateHtml } from 'mm-renderer'

// Initialize Supabase client (for production, use environment variables)
const client = createClient(
  'https://xyzcompany.supabase.co',
  'public-anon-key'
)

type PageProps = {
  params: {
    path: string
  }
}

// ✅ SEO Note:
// Instead of fetching by numeric ID (e.g., `.eq("id", 2)`),
// fetch by a human-readable `path` or slug (e.g., `.eq("path", params.path)`).
// This improves search engine discoverability and makes URLs cleaner.
export default async function Page({ params }: PageProps) {
  const { data, error } = await client
    .from('mm_posts')
    .select('*')
    .eq('id', params.id) //  <-- .eq("path", params.path)
    .single()

  if (error || !data) {
    return notFound()
  }

  const pageData = data.page_data
    ? JSON.parse(data.page_data)
    : undefined

  const css = pageData
    ? generateCss(pageData.pages[0].nodes, pageData.widgetGroups)
    : ''

  return (
    <div>
      {/* Inject dynamic CSS into the page */}
      {css && <style dangerouslySetInnerHTML={{ __html: css }} />}

      {/* Render the generated HTML */}
      <div
        dangerouslySetInnerHTML={{
          __html: generateHtml(pageData.pages[0].nodes, { t: (k: string) => k })
        }}
      />
    </div>
  )
}

// Optional: Dynamically set page metadata for SEO
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const { data } = await client
    .from('mm_posts')
    .select('title, description, image')
    .eq('path', params.path)
    .single()

  if (!data) {
    return {}
  }

  return {
    title: data.title || 'MM Base',
    description: data.description || '',
    openGraph: {
      title: data.title || '',
      description: data.description || '',
      images: data.image ? [data.image] : []
    }
  }
}

Nuxt

// pages/content/[content].vue

<template>
    <div>
        <div v-if="pending">Loading…</div>
        <div v-else-if="error">Failed to load.</div>

        <div
            v-else-if="pageData"
            v-html="generateHtml(pageData.pages[0].nodes, { t: i18n.t })"></div>
    </div>
</template>

<script setup lang="ts">
    import { createClient } from "@supabase/supabase-js"
    import { generateCss, generateHtml } from "mm-renderer"

    // In actual apps, manage them as .env
    const client = createClient("https://xyzcompany.supabase.co", "public-anon-key")

    const i18n = useI18n()

    const { data, pending, error } = await useAsyncData(
        "mm-post:2",
        async () => {
            // ✅ SEO Note:
            // Use `.eq("path", route.params.content)` (or a clean URL string) instead of numeric IDs.
            // This allows search engines to crawl and index pages by their canonical URL
            // rather than opaque IDs, improving SEO discoverability and link sharing
            const { data, error } = await client
                .from("mm_posts")
                .select("*")
                .eq("id", 2) //  <-- .eq("path", route.params.content)
                .single()
            if (error) throw error
            return data
        },
        { lazy: import.meta.client }
    )

    const pageData = computed(() => {
        return data.value?.page_data ? JSON.parse(data.value?.page_data) : undefined
    })

    onMounted(() => {
        if (!data.value?.public) alert("This page is not public")
    })

    useSeoMeta({
        title: () => data.value?.title || "MM Base",
        description: () => data.value?.description || "",
        ogTitle: () => data.value?.title || "",
        ogDescription: () => data.value?.description || "",
        ogImage: () => data.value?.image || "",
    })

    useHead(() => {
        const css = pageData.value
            ? generateCss(pageData.value.pages[0].nodes, pageData.value.widgetGroups)
            : ""

        return {
            style: css ? [{ innerHTML: css, key: "mm-renderer-css" }] : [],
        }
    })
</script>