03 - Usando uma lib para Markdown

pnpm install remark remark-parse remark-html
pnpm install --save-dev @types/remark-parse

Ver depois as Libs:

https://github.com/remarkjs/remark-rehype

https://github.com/rehypejs/rehype-highlight

https://github.com/rehypejs/rehype-sanitize

Helper para converter markdown -> HTML

Crie lib/markdown.ts

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkHtml from 'remark-html';

export async function markdownToHtml(markdown: string): Promise<string> {
  const file = await unified()
    .use(remarkParse)
    .use(remarkHtml)
    .process(markdown);

  return String(file);
}

Criar app/docs/[...slug]/page.tsx

import { supabaseServer } from '@/lib/supabaseServer';
import { supabaseAdmin } from '@/lib/supabaseAdmin';
import { markdownToHtml } from '@/lib/markdown';
import { redirect, notFound } from 'next/navigation';

interface DocPageProps {
  params: { slug: string[] };
}

export default async function DocPage({ params }: DocPageProps) {
  const slug = params.slug.join('/'); // 'financeiro/relatorio-mensal'
  const supabase = supabaseServer();

  // 1) autenticação
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) {
    redirect('/login');
  }

  // 2) buscar doc que o usuário PODE ver (RLS + grupos + pastas)
  const { data: doc, error } = await supabase
    .from('app_docs')
    .select('id, title, bucket, storage_path')
    .eq('slug', slug)
    .maybeSingle();

  if (!doc || error) {
    notFound(); // sem permissão ou não existe
  }

  // 3) baixar markdown do Storage
  const { data: file, error: sError } = await supabaseAdmin
    .storage
    .from(doc.bucket)
    .download(doc.storage_path);

  if (!file || sError) {
    console.error(sError);
    notFound();
  }

  const rawMarkdown = await file.text();

  // 4) converter markdown -> HTML
  const html = await markdownToHtml(rawMarkdown);

  // 5) renderizar
  return (
    <article className="prose mx-auto p-8">
      <h1>{doc.title}</h1>

      <div
        className="mt-4"
        dangerouslySetInnerHTML={{ __html: html }}
      />
    </article>
  );
}

O risco de dangerouslySetInnerHTML aqui é mitigado pelo fato de:

Se quiser ainda mais segurança, dá pra plugar um sanitizer (ex: rehype-sanitize).


Resumo

Você agora tem: