Skip to content

markbang/memos-embed

Repository files navigation

Memos Embed Logo

Memos Embed

CI npm

Embeddable memo cards for Memos, delivered as a website and npm packages.

Features

  • Rich memo cards with themes, density presets, and extendable design tokens
  • Core HTML renderer for SSR and static-site workflows
  • Batch multi-memo fetching and shared-style list rendering for note digests and weekly roundups
  • Shared memo client for cross-embed request deduping on React and MDX pages
  • React components for single embeds and multi-memo roundups, with optional pre-fetched memo rendering
  • Web Component wrapper with exposed ::part(...) hooks
  • Iframe embed route for no-build integrations
  • Lightweight markdown support for headings, lists, task lists, quotes, links, and fenced code blocks
  • Attachment previews for images and grouped reaction badges
  • Optional auto-resizing iframe snippets via postMessage

Workspace Layout

  • apps/site: TanStack Start website (docs, playground, iframe embeds)
  • packages/memos-embed: core API + SSR HTML helpers
  • packages/memos-embed-react: React component wrapper
  • packages/memos-embed-wc: Web Component wrapper

Quick Start

pnpm install
pnpm dev

Open:

  • site: http://localhost:3000
  • playground: http://localhost:3000/playground

Build

pnpm -r build

Tests

pnpm test
pnpm test:artifacts

Validation

pnpm validate

Releasing

  • Add a changeset for package changes with pnpm changeset
  • The Publish Packages workflow versions changed packages directly on main, publishes them to npm, then creates a Git tag and a GitHub Release generated by changelogithub
  • If your main branch blocks workflow pushes or release creation, add a CHANGESETS_GITHUB_TOKEN secret with repo write access
  • Set NPM_TOKEN so package publishing can authenticate with npm

Base URL handling

baseUrl accepts any of these forms and normalizes them internally:

  • https://demo.usememos.com
  • https://demo.usememos.com/api
  • https://demo.usememos.com/api/v1

Use whichever shape you already have. The client resolves memo and user requests against /api/v1 automatically.

Usage

Core package

import { extendTheme, fetchMemo, renderMemoHtmlSnippet } from 'memos-embed'

const memo = await fetchMemo({
  baseUrl: 'https://demo.usememos.com/api/v1',
  memoId: '1',
})

const blogTheme = extendTheme('paper', {
  radius: 'var(--radius)',
  fontFamily: 'inherit',
  tokens: {
    background: 'var(--card)',
    foreground: 'var(--card-foreground)',
    border: 'var(--border)',
    accent: 'var(--primary)',
    accentForeground: 'var(--primary-foreground)',
    mutedForeground: 'var(--muted-foreground)',
    codeBackground: 'var(--muted)',
  },
})

const html = renderMemoHtmlSnippet(memo, {
  includeStyles: true,
  theme: blogTheme,
  density: 'comfortable',
  showAttachments: true,
  showReactions: true,
  linkTarget: '_blank',
})

Multiple memos on one page

import { fetchMemos, renderMemoListHtmlSnippet } from 'memos-embed'

const memos = await fetchMemos({
  baseUrl: 'https://demo.usememos.com/api/v1',
  memoIds: ['1', '2', '3'],
})

const html = renderMemoListHtmlSnippet(memos, {
  layout: 'stack',
  gap: '20px',
  theme: 'paper',
})

Shared memo client

import { createMemoClient } from 'memos-embed'

const memoClient = createMemoClient()

React

import { MemoEmbed } from '@memos-embed/react'

<MemoEmbed
  baseUrl="https://demo.usememos.com/api/v1"
  memoId="1"
  theme="glass"
  density="compact"
  linkTarget="_blank"
  showAttachments
  showReactions
/>

React with pre-fetched data

import { fetchMemo } from 'memos-embed'
import { MemoEmbed } from '@memos-embed/react'

const memo = await fetchMemo({
  baseUrl: 'https://demo.usememos.com/api/v1',
  memoId: '1',
})

<MemoEmbed memo={memo} />

React roundup component

import { MemoEmbedList } from '@memos-embed/react'

<MemoEmbedList
  baseUrl="https://demo.usememos.com/api/v1"
  memoIds={["1", "2", "3"]}
  layout="stack"
  gap="20px"
  theme="paper"
/>

React shared client

import { createMemoClient } from 'memos-embed'
import { MemoClientProvider, MemoEmbed, MemoEmbedList } from '@memos-embed/react'

const client = createMemoClient()

<MemoClientProvider client={client}>
  <MemoEmbed baseUrl="https://demo.usememos.com/api/v1" memoId="1" />
  <MemoEmbedList baseUrl="https://demo.usememos.com/api/v1" memoIds={["2", "3"]} />
</MemoClientProvider>

Use a shared client when multiple embeds on one page should reuse memo and creator fetches instead of issuing duplicate requests.

React shared client with pre-fetched data

import { createMemoClient, fetchMemo, fetchMemos } from 'memos-embed'
import { MemoClientProvider, MemoEmbed, MemoEmbedList } from '@memos-embed/react'

const client = createMemoClient()

const [heroMemo, roundupMemos] = await Promise.all([
  fetchMemo({
    baseUrl: 'https://demo.usememos.com/api/v1',
    memoId: '1',
  }),
  fetchMemos({
    baseUrl: 'https://demo.usememos.com/api/v1',
    memoIds: ['2', '3'],
  }),
])

<MemoClientProvider client={client}>
  <MemoEmbed
    baseUrl="https://demo.usememos.com/api/v1"
    memo={heroMemo}
  />
  <MemoEmbedList
    baseUrl="https://demo.usememos.com/api/v1"
    memos={roundupMemos}
  />
</MemoClientProvider>

Passing memo or memos while a MemoClientProvider is active primes the shared client cache, so later embeds for the same ids can reuse already-fetched data.

Web Component

<script type="module" src="https://unpkg.com/@memos-embed/wc@latest/dist/register.js"></script>
<memos-embed
  base-url="https://demo.usememos.com/api/v1"
  memo-id="1"
  theme="midnight"
  link-target="_blank"
  show-tags="true"
  show-attachments="true"
  show-reactions="true"
></memos-embed>
memos-embed::part(container) {
  border-radius: 24px;
}

Iframe

import { renderIframeHtml } from 'memos-embed'

const iframe = renderIframeHtml({
  embedBaseUrl: 'https://your-site.com',
  baseUrl: 'https://demo.usememos.com/api/v1',
  memoId: '1',
  height: 240,
  autoResize: true,
})

autoResize works through a postMessage handshake keyed by frameId. renderIframeHtml() keeps that wiring aligned for you. If you hand-roll the iframe markup, make sure the iframe element id matches the frameId query param you send to /embed/:memoId.

For pages with multiple embeds, give each iframe a distinct frameId to keep resize events scoped correctly.

Blog integration examples

  • examples/next-mdx: Next.js App Router + server-fetched memo data
  • examples/mdx-components: reusable MDX component pattern for React-based blogs
  • examples/astro-blog: Astro + MDX blog usage
  • examples/static-html: iframe and Web Component copy-paste examples for static sites and CMS pages

Development Notes

  • The site uses source aliases so local changes in packages/* show up immediately in apps/site
  • The playground keeps its configuration in the URL for easy sharing
  • Package builds are powered by tsup; site builds use Vite + TanStack Start
  • CI runs pnpm validate, including lint, Biome checks, tests, package-consumer smoke tests, and the site build
  • Releases are managed with Changesets on main: the workflow versions packages, commits the release changes, publishes to npm, and creates a Git tag plus a changelogithub-powered GitHub Release automatically

About

Memos Embed — Type‑safe memo card embeds for usememos.com with a ready‑to‑deploy site plus React and Web Component packages.

Topics

Resources

Stars

Watchers

Forks

Contributors