Last week, I launched FreeCard.app - a completely free business card and QR code generator. In this post, I'll share the journey, tech decisions, challenges, and lessons learned.
๐ฏ The Problem
I was frustrated with existing business card generators:
โ Most require signup just to see your design
โ "Free" tools add watermarks to exports
โ Premium features locked behind $10-20/month paywalls
โ Overly complicated interfaces for a simple task
I wanted something that just works. Enter a name, pick a template, download. Done.
๐ก The Solution
FreeCard.app - A genuinely free business card generator with:
โ
25+ professional templates
โ
Automatic QR code generation (vCard format)
โ
Custom colors and fonts
โ
PNG, PDF, and vCard export
โ
Email signature generator
โ
No signup required
โ
No watermarks, ever
Business model? Simple - Google AdSense. No premium tier, no upsells.
๐ ๏ธ Tech Stack
Frontend: Next.js 14 (App Router)
Language: TypeScript
Styling: Tailwind CSS + shadcn/ui
State: Zustand
Database: MongoDB + Mongoose
Auth: NextAuth.js v5
Export: html-to-image + jsPDF
QR: qrcode.react
Hosting: Vercel
Why This Stack?
Next.js 14 App Router - Server components by default = faster initial load. The new App Router is mature enough for production now.
Zustand over Redux - For a tool like this, Zustand's simplicity wins. No boilerplate, just works:
typescript// store/useCardStore.ts
import { create } from 'zustand';
interface CardStore {
card: CardData;
setField: (field: string, value: string) => void;
setTemplate: (templateId: string) => void;
}
export const useCardStore = create<CardStore>((set) => ({
card: initialCard,
setField: (field, value) =>
set((state) => ({ card: { ...state.card, [field]: value } })),
setTemplate: (templateId) =>
set((state) => ({ card: { ...state.card, template: templateId } })),
}));
shadcn/ui - Not a component library, but copy-paste components. Full control, great defaults, accessible out of the box.
๐จ The Template System
One of the trickiest parts was building a flexible template system. Each template needs to:
Accept the same props (name, title, colors, etc.)
Render differently based on design
Be exportable as PNG/PDF
Here's a simplified version:
typescript// templates/ModernDark.tsx
interface TemplateProps {
card: CardData;
showQR?: boolean;
}
export function ModernDark({ card, showQR = true }: TemplateProps) {
const { fullName, title, email, phone, colors } = card;
return (
<div
className="w-[350px] h-[200px] rounded-lg p-6 flex"
style={{ backgroundColor: colors.background }}
>
<div className="flex-1">
<h2
className="text-xl font-bold"
style={{ color: colors.text }}
>
{fullName || 'Your Name'}
</h2>
<p style={{ color: colors.primary }}>{title || 'Your Title'}</p>
{/* ... more fields */}
</div>
{showQR && (
<QRCodeSVG
value={generateVCard(card)}
size={80}
bgColor="transparent"
fgColor={colors.text}
/>
)}
</div>
);
}
๐ค The Export Challenge
Exporting HTML to PNG/PDF sounds simple until you actually try it. Here's what worked:
PNG Export
typescriptimport { toPng } from 'html-to-image';
export async function exportToPNG(elementId: string) {
const element = document.getElementById(elementId);
if (!element) throw new Error('Element not found');
const dataUrl = await toPng(element, {
quality: 1,
pixelRatio: 3, // High resolution
backgroundColor: '#ffffff',
});
// Trigger download
const link = document.createElement('a');
link.download = 'business-card.png';
link.href = dataUrl;
link.click();
}
PDF Export (Standard Business Card Size)
typescriptimport jsPDF from 'jspdf';
import { toPng } from 'html-to-image';
export async function exportToPDF(elementId: string) {
const element = document.getElementById(elementId);
const dataUrl = await toPng(element, { pixelRatio: 3 });
// Standard business card: 3.5" x 2" (88.9mm x 50.8mm)
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'mm',
format: [88.9, 50.8],
});
pdf.addImage(dataUrl, 'PNG', 0, 0, 88.9, 50.8);
pdf.save('business-card.pdf');
}
vCard Generation
QR codes encode vCard data so people can scan and save contacts:
typescriptexport function generateVCard(card: CardData): string {
return [
'BEGIN:VCARD',
'VERSION:3.0',
`FN:${card.fullName}`,
`ORG:${card.company || ''}`,
`TITLE:${card.title || ''}`,
`EMAIL:${card.email || ''}`,
`TEL:${card.phone || ''}`,
`URL:${card.website || ''}`,
'END:VCARD',
].join('\n');
}
๐ Challenges & Solutions
- Fonts Not Rendering in Export Problem: Custom fonts appeared as system fonts in PNG exports. Solution: Ensure fonts are fully loaded before export: typescriptawait document.fonts.ready; await new Promise(resolve => setTimeout(resolve, 100)); const dataUrl = await toPng(element);
- QR Code Sizing Problem: QR codes were either too big (overwhelming) or too small (unscannable). Solution: Fixed size relative to card, minimum 60px for reliable scanning: typescript
- Color Picker Performance Problem: Real-time preview lagged with continuous color changes. Solution: Debounced updates: typescriptconst debouncedSetColor = useMemo( () => debounce((color: string) => setColor('primary', color), 50), [] ); ๐ Results (First Week)
๐ Launched on Product Hunt
๐ 500+ unique visitors
๐พ 200+ cards created
๐ Backlinks from BetaList, AlternativeTo
๐ฐ $0 revenue (AdSense pending approval)
๐ธ Cost Breakdown
ItemMonthly CostVercel Hosting$0 (Hobby plan)MongoDB Atlas$0 (Free tier)Domain (.app)~$1.25 ($15/year)Total~$1.25/month
That's it. A fully functional SaaS-like product for about $15/year.
๐ Lessons Learned
- Ship Fast, Iterate Later I launched with 5 templates. Now there are 25. The first version was "good enough" - users told me what they actually wanted.
- "Free" is a Feature In a market full of "freemium" tools with aggressive upsells, being genuinely free is a differentiator.
- Zustand > Redux for Small Projects If your state fits in one file, you don't need Redux. Zustand's API is a joy to use.
- shadcn/ui is Amazing Copy-paste components mean you own the code. No fighting with library abstractions.
- The App Router is Ready After months of "should I use Pages or App Router?" - just use App Router. It's stable and the DX is great. ๐ฎ What's Next
More templates (targeting 50+)
NFC business card support
Public shareable links (freecard.app/c/username)
LinkedIn profile import
Template marketplace (user-submitted designs)
๐ Try It Out
If you need a business card, give it a try:
๐ FreeCard.app
No signup, no watermarks, no BS. Just free business cards.
What do you think? I'd love to hear your feedback in the comments. What features would make this more useful for you?
If you found this useful, consider:
โญ Upvoting on Product Hunt
๐ฆ Following me on Twitter for more indie hacker content
๐ง Sharing with someone who needs business cards
Tags
Top comments (0)