If you've used ChatGPT, Claude, or any other AI assistant, you've faced this problem: How do you share the output?
The current workflow is painful:
- Take a screenshot (loses formatting)
- Copy-paste to Google Docs (breaks code blocks)
- Share via email (messy formatting)
- Post on Twitter (character limit kills context)
I built Markdrop to solve this: a modern pastebin specifically designed for sharing AI-generated content with proper markdown rendering, syntax highlighting, and team collaboration features.
The Problem with Screenshots
For Developers:
- Code formatting breaks
- Syntax highlighting lost
- Can't copy-paste code
- Screenshots aren't searchable
- No version control
For Teams:
- Hard to collaborate on AI outputs
- No commenting or feedback
- Can't track changes
- Screenshots clutter chat
- Lost in message history
For Everyone:
- Static, can't edit
- Hard to find later
- No organization
- Privacy concerns (public screenshots)
Why Existing Solutions Fall Short
I evaluated existing pastebin services:
Traditional Pastebins (Pastebin.com, Gist)
- ❌ No markdown preview
- ❌ Outdated UI
- ❌ No team features
- ❌ Ad-heavy
Note-taking Apps (Notion, Google Docs)
- ❌ Too heavy for quick sharing
- ❌ Requires account to view
- ❌ Not optimized for code
- ❌ Slow to load
Code Sharing (CodePen, JSFiddle)
- ❌ Only for runnable code
- ❌ Not for prose/markdown
- ❌ Overkill for simple sharing
Enter Markdrop: The Solution
Markdrop is a markdown-first pastebin built for the AI era:
✅ Anonymous paste creation (no account needed) ✅ Live markdown preview with syntax highlighting ✅ Team collaboration with projects and collections ✅ 30-day auto-expiration for free tier ✅ Rate limiting without requiring sign-up ✅ Like/unlike functionality ✅ Mobile-responsive split-pane editor
Tech Stack
I chose Next.js 15 with the App Router for modern React features and excellent DX:
- Next.js 15: App Router, Server Components, Server Actions
- Drizzle ORM: Type-safe SQL with PostgreSQL
- Better Auth: Flexible authentication with social providers
- shadcn/ui: Beautiful, accessible UI components
- Vercel: Deployment and hosting
- Neon PostgreSQL: Serverless Postgres database
Key Features Walkthrough
1. Anonymous Paste Creation
No account required. Just paste, save, share:
// app/actions/paste.ts
'use server'
import { nanoid } from 'nanoid'
import { db } from '@/lib/db'
import { pastes } from '@/lib/schema'
export async function createPaste(content: string, expiresInDays = 30) {
const slug = nanoid(10) // Short, unique ID
const expiresAt = new Date()
expiresAt.setDate(expiresAt.getDate() + expiresInDays)
const [paste] = await db.insert(pastes).values({
slug,
content,
expiresAt,
createdAt: new Date(),
}).returning()
return paste
}
2. Rate Limiting Without Accounts
IP-based rate limiting with hashed IPs for privacy:
// lib/rate-limit.ts
import { createHash } from 'crypto'
import { Redis } from '@upstash/redis'
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
})
function hashIP(ip: string): string {
return createHash('sha256')
.update(ip + process.env.RATE_LIMIT_SALT!)
.digest('hex')
}
export async function checkRateLimit(ip: string): Promise<boolean> {
const hashedIP = hashIP(ip)
const key = `ratelimit:${hashedIP}`
const count = await redis.incr(key)
if (count === 1) {
// First request, set expiration
await redis.expire(key, 3600) // 1 hour
}
return count <= 10 // Max 10 pastes per hour
}
3. Team Collaboration
Projects group related pastes:
// lib/schema.ts (Drizzle)
export const teams = pgTable('teams', {
id: uuid('id').primaryKey().defaultRandom(),
name: text('name').notNull(),
slug: text('slug').notNull().unique(),
createdAt: timestamp('created_at').defaultNow(),
})
export const projects = pgTable('projects', {
id: uuid('id').primaryKey().defaultRandom(),
teamId: uuid('team_id').references(() => teams.id),
name: text('name').notNull(),
description: text('description'),
})
export const pastes = pgTable('pastes', {
id: uuid('id').primaryKey().defaultRandom(),
slug: text('slug').notNull().unique(),
content: text('content').notNull(),
projectId: uuid('project_id').references(() => projects.id),
userId: uuid('user_id').references(() => users.id),
expiresAt: timestamp('expires_at'),
createdAt: timestamp('created_at').defaultNow(),
})
4. Live Markdown Preview
Split-pane editor with Monaco-like experience:
// components/editor.tsx
'use client'
import { useState } from 'react'
import { Textarea } from '@/components/ui/textarea'
import { MarkdownPreview } from '@/components/markdown-preview'
export function Editor({ initialContent = '' }) {
const [content, setContent] = useState(initialContent)
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 h-[600px]">
<div className="border rounded-lg p-4">
<Textarea
value={content}
onChange={(e)=> setContent(e.target.value)}
placeholder="Paste your markdown here..."
className="w-full h-full font-mono resize-none"
/>
</div>
<div className="border rounded-lg p-4 overflow-auto">
<MarkdownPreview content={content} />
</div>
</div>
)
}
5. GitHub-Flavored Markdown
Using react-markdown with syntax highlighting:
// components/markdown-preview.tsx
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
export function MarkdownPreview({ content }: { content: string }) {
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
code({ node, inline, className, children, ...props }) {
const match= /language-(\w+)/.exec(className || '')
return !inline && match ? (
<SyntaxHighlighter
style={oneDark}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
)
},
}}
>
{content}
</ReactMarkdown>
)
}
Architecture Decisions
Why Next.js App Router?
Server Components reduce client-side JavaScript:
// app/[slug]/page.tsx
import { db } from '@/lib/db'
import { pastes } from '@/lib/schema'
import { eq } from 'drizzle-orm'
import { MarkdownPreview } from '@/components/markdown-preview'
export default async function PastePage({ params }) {
const paste = await db.query.pastes.findFirst({
where: eq(pastes.slug, params.slug),
})
if (!paste) {
return <div>Paste not found</div>
}
return (
<div className="container mx-auto py-8">
<MarkdownPreview content={paste.content} />
</div>
)
}
No client-side fetching needed—SSR by default!
Why Drizzle ORM?
Type-safe SQL with excellent TypeScript inference:
// Fully typed query
const userPastes = await db.query.pastes.findMany({
where: eq(pastes.userId, user.id),
with: {
project: {
with: {
team: true,
},
},
},
orderBy: desc(pastes.createdAt),
})
// Type: Paste & { project: Project & { team: Team } }[]
Why Better Auth?
Flexible auth that doesn't lock you into a specific provider:
// lib/auth.ts
import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: 'pg',
}),
socialProviders: {
github: {
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
},
google: {
clientId: process.env.GOOGLE_ID!,
clientSecret: process.env.GOOGLE_SECRET!,
},
},
})
Monetization Strategy
Free Tier:
- Anonymous pastes (30-day expiration)
- Public pastes only
- Rate limited (10/hour)
Pro Tier ($5/month):
- Unlimited pastes
- Private pastes
- Team collaboration
- Projects and collections
- Custom expiration
- No rate limits
This aligns with how developers think: try it free, upgrade when you need team features.
Deployment: Vercel + Neon
Vercel for hosting:
- Zero-config deployment
- Edge functions for rate limiting
- Analytics built-in
Neon PostgreSQL for database:
- Serverless (scales to zero)
- Generous free tier
- Automatic backups
- Branch databases for preview deployments
# Deploy
vercel --prod
# Database migrations
pnpm drizzle-kit push:pg
Lessons Learned
1. Start Anonymous-First
Don't require sign-up for basic features. Lower friction = more users.
2. Rate Limiting is Hard
IP-based limiting works but has issues:
- VPNs share IPs
- Mobile networks rotate IPs
- Hashed IPs lose some debuggability
Consider device fingerprinting for better accuracy.
3. Markdown Edge Cases
Users paste wild stuff:
- 50,000+ line files (crashes browser)
- Malicious HTML in markdown
- Deeply nested blockquotes
Solutions:
- Limit paste size (500KB max)
- Sanitize HTML in preview
- Lazy-load large pastes
4. Monaco vs Textarea
I tried Monaco editor first (like VS Code):
- ❌ 2MB bundle size
- ❌ Overkill for simple pasting
- ❌ Mobile support issues
Switched to enhanced <textarea>:
- ✅ 0KB (native)
- ✅ Works everywhere
- ✅ Accessible
Performance Optimizations
1. Server Components for Static Content
// Heavy markdown rendering happens server-side
export default async function PastePage({ params }) {
const paste = await db.query.pastes.findFirst({
where: eq(pastes.slug, params.slug),
})
// Pre-rendered HTML sent to client
return <MarkdownPreview content={paste.content} />
}
2. Edge Caching
export const revalidate = 3600 // 1 hour
export async function generateStaticParams() {
// Pre-render popular pastes
const popular = await db.query.pastes.findMany({
where: gt(pastes.views, 100),
limit: 100,
})
return popular.map((paste) => ({
slug: paste.slug,
}))
}
3. Database Indexes
export const pastes = pgTable(
'pastes',
{
id: uuid('id').primaryKey().defaultRandom(),
slug: text('slug').notNull().unique(),
userId: uuid('user_id'),
createdAt: timestamp('created_at').defaultNow(),
},
(table) => ({
slugIdx: index('slug_idx').on(table.slug),
userIdIdx: index('user_id_idx').on(table.userId),
createdAtIdx: index('created_at_idx').on(table.createdAt),
})
)
Future Roadmap
Short-term:
- API for programmatic paste creation
- CLI tool (
markdrop push file.md) - Embed support (
<iframe>for pastes) - Syntax highlighting themes
Long-term:
- Collaborative editing (multiplayer)
- Version history/diffs
- Comments and annotations
- AI-powered search across pastes
- Export to PDF/HTML
Conclusion
Markdrop solves a real problem: sharing AI-generated content is painful with existing tools. By focusing on markdown, anonymous creation, and team features, it fills a gap in the market.
The tech stack (Next.js 15, Drizzle, Better Auth) provides a solid foundation that's both performant and developer-friendly. Starting with a generous free tier and clear upgrade path creates a sustainable business model.
If you're building a SaaS, focus on:
- Low friction (anonymous access)
- Clear value prop (solves a specific pain)
- Modern DX (fast, clean, no cruft)
- Monetization from day one (even if small)
Whether you're sharing ChatGPT outputs, code snippets, or documentation, Markdrop makes it frictionless.
Try it: markdrop.app