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>