Blog
A built-in blog with a Tiptap rich-text editor, inline image uploads, and standard SEO.
Launchy ships with a simple blog — admins write rich content via Tiptap and readers see a standard article layout.
URLs
| Route | What |
|---|---|
/blog | Listing of published posts |
/blog/[slug] | Single post |
There is no RSS feed route shipped by default. If you want one, add app/feed.xml/route.ts.
Editor (admin side)
/admin/blog shows the post list. The editor at app/admin/blog/blog-editor.tsx uses Tiptap with these extensions:
@tiptap/starter-kit— headings, paragraphs, lists, blockquotes, bold/italic, etc.@tiptap/extension-link@tiptap/extension-placeholder@tiptap/extension-image@tiptap/extension-table-kit
Content is saved as HTML in posts.content.
Inline image uploads
The editor's image button triggers a file input that POSTs to /api/upload (same endpoint as product thumbnails). The returned R2 URL is inserted into the post.
Code blocks
The Tiptap starter-kit supports code blocks, but no syntax highlighting library is wired up. If you want syntax-highlighted output, add Shiki or Prism:
bun add shikiThen transform the post HTML on render (post-process <pre><code> blocks in your /blog/[slug]/page.tsx).
Draft vs published
posts.status (postStatus enum): draft | published.
Published posts appear on /blog. Drafts are admin-only.
Scheduled posts
Not implemented. publishedAt is set when an admin clicks Publish, not at a future time. If you need scheduling, add a cron route app/api/cron/publish-scheduled-posts that flips drafts with publishedAt <= now() to published.
SEO on post pages
Each post page (app/blog/[slug]/page.tsx) sets <title>, <meta description> from posts.title / posts.excerpt, and includes a canonical URL.
For social sharing, the post's image_url (featured image) is used as the OG image.
Schema
From lib/db/schema.ts:
| Column | Type |
|---|---|
id | serial, PK |
title | varchar 200 |
slug | varchar 250, unique |
excerpt | text |
content | text (HTML) |
image_url | text |
status | enum (draft / published) |
author_id | text → user.id |
published_at | timestamp nullable |
Disabling the blog
To remove the blog entirely:
- Delete
app/blog/,app/admin/blog/ - Drop the
poststable in your schema + migration - Remove the "Blog" link from the header