Turn Any Side Project Into a Digital Product
I built an app for my mom's food business. Then I ripped out every hardcoded reference, built a setup wizard, added feature flags, and turned one codebase into 9 products selling for $14–59 each. Here's the exact framework.
The Side Project Graveyard
You know the pattern. You get excited about a project on a Friday night. You scaffold it, get the core feature working, maybe even deploy it. Then Monday comes. Work takes over. The repo sits there, accumulating GitHub's little "this branch is X commits behind" warnings until you archive it six months later.
I've killed dozens of projects this way. And every time, I told myself the same lie: "I'll come back to it when I have more time."
The problem isn't time. The problem is that side projects have no stakes. Nobody is waiting for your update. Nobody paid you. Nobody will notice if you stop. The project exists in a vacuum where the only motivation is your own enthusiasm, and enthusiasm is a terrible long-term fuel source.
But here's what I've learned: the side projects that survive are the ones built for a real person with a real problem. Not a hypothetical user persona. An actual human who will text you at 11pm saying "the receipt thing isn't working."
That person, for me, was my mom.
The Mom App
My mom runs a small food business. Dumplings, vareniki, the whole Eastern European comfort food catalog. She was managing everything with a combination of paper notebooks, calculator apps, and an increasingly chaotic WhatsApp group with her regular customers.
So I built her an app. Internally I called it Dumpling Hub because I'm hilarious. It started simple — a receipt generator and a customer list. Then it grew. Inventory tracking because she kept running out of flour at the worst times. Profit calculations because she was underpricing her pelmeni. An AI assistant that could answer questions about her recipes and business in both English and Russian. A notification system for order updates.
Over about three months, Dumpling Hub became a genuinely useful full-stack business management app. Supabase backend, React frontend, Edge Functions handling the AI features, Stripe-ready invoicing. The whole thing.
And then one day I looked at it and thought: this isn't a dumpling app. This is a small business app that happens to have dumplings in it.
Every feature I'd built — invoicing, inventory, client management, profit tracking — was universal. The only thing specific to my mom's business was the word "dumpling" scattered across the codebase like confetti. Hardcoded business names in the UI. Hardcoded context in the AI prompts. Hardcoded assumptions everywhere.
That realization is worth the entire post. If your side project solves a real problem for one person, it probably solves it for thousands. You just need to remove the one-person assumptions.
The Genericization Framework
I didn't do this in one marathon session. That's a recipe for burnout and bugs. Instead, I broke it into 5 focused sessions, each with a clear goal and a natural stopping point. The whole thing took about two weeks of evening work.
The framework:
- Kill the Hardcoding — create a config interface, strip every business-specific reference
- Setup Wizard — build a first-run experience so strangers can configure the app for their business
- AI Prompt Refactor — make every AI feature pull context from the database, not from hardcoded strings
- Feature Flags — split the monolith into toggleable kits so you can sell subsets
- Packaging — READMEs, setup scripts, zip bundles, delivery infrastructure
Let me walk through each one.
Session 1: Kill the Hardcoding
This is the unglamorous foundation. You're going to grep your entire codebase for anything specific to your original use case and replace it with configuration.
I started by creating a BusinessConfig interface that described everything the app needed to know about the business it was serving:
// src/types/business.ts
export interface BusinessConfig {
businessName: string;
ownerName: string;
businessType: string; // "restaurant" | "bakery" | "retail" | etc.
currency: string; // "USD" | "EUR" | "RUB" | etc.
language: string; // primary language
secondaryLanguage?: string; // for bilingual support
timezone: string;
logoUrl?: string;
contactEmail?: string;
contactPhone?: string;
features: {
invoicing: boolean;
inventory: boolean;
recipes: boolean;
aiAssistant: boolean;
notifications: boolean;
profitTracking: boolean;
clientIntelligence: boolean;
};
}
Then I created a business_settings table in Supabase with these fields as columns. Every component that previously said "Dumpling Hub" now pulled from this table via a React context provider.
The key principle: env vars for developer defaults, database for user configuration. I set up VITE_DEFAULT_BUSINESS_NAME, VITE_DEFAULT_CURRENCY, etc. so the app had sensible fallbacks before the database was populated, but the real values always came from business_settings.
The tedious part was the actual find-and-replace. I searched for every instance of the original business name, every hardcoded "dumpling" and "vareniki" reference, every assumption about currency being rubles. There were 47 files with hardcoded references. Not a fun evening, but a necessary one.
Tip
Run grep -rn "YourBusinessName\|your-specific-thing" src/ and pipe it to a file. Work through it line by line. It's boring. Do it anyway. Every hardcoded string you miss is a bug report from a future customer.
Session 2: The Setup Wizard
This was the session that turned Dumpling Hub from "my app" into "anyone's app." If a stranger downloads your code and the first thing they see is a blank screen or, worse, your mom's business name — they're gone. You get one chance at a first impression.
I built a 7-step SetupWizard component that runs on first launch (detected by checking if business_settings has any rows):
- Welcome — what this app does, what you'll need (2 minutes of info)
- Business Info — name, type, owner name, logo upload
- Locale — currency, timezone, primary/secondary language
- Features — toggle which kits to enable (checkboxes with descriptions)
- Inventory Categories — pre-populated templates based on business type, editable
- AI Preferences — tone, language, what the assistant should know about the business
- Confirmation — review everything, one-click save to Supabase
The wizard writes everything to business_settings and flips a setup_complete boolean. From that point on, the app is theirs. Their name in the header. Their currency on every invoice. Their language in the AI responses.
One thing I got right: step 4 (feature toggles) is critical for packaging later. By letting users enable/disable features at setup time, I was already building the infrastructure for selling subsets of the app as separate products. I didn't realize this yet. Sometimes you build the right abstraction by accident.
Session 3: AI Prompt Refactor
This was the scariest session. The app had 6 Edge Functions that used AI — recipe suggestions, inventory predictions, customer message drafts, a general Q&A assistant, profit analysis summaries, and a bilingual translator. Every single one had hardcoded context baked into the system prompts.
The original recipe assistant prompt literally started with "You are a culinary assistant for a Eastern European dumpling restaurant..."
I refactored all 6 Edge Functions to follow the same pattern:
// Inside each Edge Function
const { data: settings } = await supabase
.from('business_settings')
.select('*')
.single();
const systemPrompt = buildPrompt(settings, functionSpecificContext);
// buildPrompt pulls business name, type, language,
// inventory categories, and tone preferences from the DB
// and constructs the prompt dynamically
The buildPrompt utility became a shared module. It takes the business settings and a function-specific context object, then assembles a system prompt that's relevant to whatever business is running the app. A bakery gets baking terminology. A retail shop gets inventory language. A food truck gets mobile-specific suggestions.
This is where something like my Brain Kit approach comes in handy, by the way — if you're building AI features that need to adapt to different contexts, having a structured way to manage and inject knowledge makes the prompt engineering dramatically easier. I use a similar pattern for all my AI-powered builds now.
The honest truth: the AI features got better after genericization. When you're forced to make context explicit and structured instead of implicit and hardcoded, the prompts become more precise. Constraints breed quality.
Session 4: Feature Flags
This is where the "one codebase, many products" magic happens. I needed a way to compile different versions of the app with different features enabled — without maintaining separate branches.
I created a kits.ts file that defines every feature kit:
// src/config/kits.ts
export type KitId =
| 'invoicing'
| 'receipts'
| 'inventory'
| 'profit-tracking'
| 'client-intelligence'
| 'notifications'
| 'recipes'
| 'ai-assistant';
export interface KitDefinition {
id: KitId;
name: string;
description: string;
routes: string[]; // which routes this kit owns
components: string[]; // which components to render
edgeFunctions: string[];// which serverless functions it needs
}
const ACTIVE_KITS: KitId[] =
(import.meta.env.VITE_ACTIVE_KITS || 'all')
.split(',')
.map((k: string) => k.trim()) as KitId[];
export function isKitActive(kit: KitId): boolean {
return ACTIVE_KITS.includes('all') || ACTIVE_KITS.includes(kit);
}
Then I built a KitGuard component — a simple wrapper that conditionally renders children based on whether their kit is active:
// src/components/KitGuard.tsx
export function KitGuard({
kit,
children,
fallback = null
}: {
kit: KitId;
children: React.ReactNode;
fallback?: React.ReactNode;
}) {
return isKitActive(kit) ? <>{children}</> : <>{fallback}</>;
}
Every feature section in the app gets wrapped in a KitGuard. The router skips routes for disabled kits. The sidebar hides nav items for disabled kits. The setup wizard only shows steps for enabled kits.
The beauty: setting VITE_ACTIVE_KITS=invoicing,receipts at build time gives you a completely different product than VITE_ACTIVE_KITS=inventory,recipes,ai-assistant. Same codebase. Same deploy process. Different product.
This isn't tree-shaking — disabled kit code still ships in the bundle. For a downloadable product sold at $14–59, that's fine. The user isn't paying for bundle size optimization. They're paying for a working app that solves their problem. If you're building a SaaS, you'd want proper code splitting. For digital products, KitGuard is more than enough.
Session 5: Packaging
The final session is about making the product shippable. This is where most developer-sellers fall apart. The code works on your machine. Cool. Can a stranger with intermediate dev skills get it running in under 10 minutes?
I wrote 8 kit-specific READMEs. Not auto-generated docs. Actual human-written guides that assume the reader knows React and Supabase basics but nothing about my codebase. Each README covers:
- What the kit does (with screenshots)
- Prerequisites (Node, Supabase project, Stripe account if applicable)
- Step-by-step setup (clone, install, configure env, run migrations, start)
- Environment variables reference
- Customization guide (how to change colors, add fields, modify prompts)
- Common issues and fixes
Then I wrote a setup.sh script that automates the tedious parts:
#!/bin/bash
# setup.sh — Run this after cloning
echo "Setting up your Dash Biz app..."
# Check prerequisites
command -v node >/dev/null 2>&1 || { echo "Node.js required"; exit 1; }
command -v npx >/dev/null 2>&1 || { echo "npx required"; exit 1; }
# Install dependencies
npm install
# Copy env template
if [ ! -f .env ]; then
cp .env.example .env
echo "Created .env — fill in your Supabase credentials"
fi
# Run Supabase migrations
echo "Ready to run migrations? (requires Supabase CLI)"
read -p "Continue? [y/N] " confirm
if [[ $confirm == [yY] ]]; then
npx supabase db push
fi
echo "Setup complete. Run 'npm run dev' to start."
Finally, the packing script. build-kits.sh iterates over each product configuration, sets the right environment variables, builds the project, and creates a zip:
#!/bin/bash
# build-kits.sh — Package all product variants
KITS=(
"invoicing:invoicing,receipts"
"inventory:inventory"
"recipes:recipes,ai-assistant"
"profit:profit-tracking"
"clients:client-intelligence,notifications"
"notifications:notifications"
"ai-assistant:ai-assistant"
"full-suite:all"
)
for entry in "${KITS[@]}"; do
NAME="${entry%%:*}"
FLAGS="${entry##*:}"
echo "Building: dash-biz-${NAME}"
VITE_ACTIVE_KITS=$FLAGS npm run build
# Package with the right README and config
cp "docs/README-${NAME}.md" dist/README.md
cp "configs/${NAME}.env.example" dist/.env.example
cp setup.sh dist/
cd dist && zip -r "../output/dash-biz-${NAME}.zip" . && cd ..
rm -rf dist
done
echo "Built ${#KITS[@]} packages in ./output/"
I uploaded all 9 zips (8 individual kits + 1 full suite) to Supabase Storage. When someone buys through Stripe, a webhook fires, and they get a download link. No manual fulfillment. No email attachments. It just works.
The Math
Here's what one side project became:
- Invoicing + Receipts Kit — $19
- Inventory Kit — $14
- Recipe + AI Kit — $24
- Profit Tracking Kit — $14
- Client Intelligence Kit — $19
- Notifications Kit — $14
- AI Assistant Kit — $19
- Full Suite — $49
- Dash Biz Suite (all kits unified + premium support README) — $59
Buying every kit individually would cost $123. The full suite at $49 saves 60%. The premium suite at $59 adds a more detailed customization guide and priority in my inbox if you get stuck. Bundle economics are real. Most people buy the $49 or $59 tier because the per-kit prices make the bundle feel like a steal.
I'm not going to quote revenue numbers because the store has only been live for a few weeks. But I will say this: even a handful of sales per month at these price points covers my coffee habit, my domain renewals, and my Supabase Pro tier. That's not life-changing money. It's project-sustaining money. And project-sustaining money is what keeps side projects alive.
The real leverage: every improvement I make to the codebase improves all 9 products simultaneously. One bug fix, 9 products updated. One new feature, potentially a new kit or an upgrade to an existing one. The marginal cost of maintaining 9 products is nearly identical to maintaining 1.
What Makes a Side Project Productizable
Not every side project can become a product. I want to be honest about the criteria because I think the "just ship it" crowd oversimplifies this.
Your side project is a good candidate if:
- It solves a workflow problem, not a novelty problem. Invoicing, inventory, scheduling, tracking — these are boring, universal needs. A "rate my cat's mood using AI" app is fun but has no market.
- Someone is already using it. If your app has zero real users (including yourself), you're guessing about product-market fit. My mom used Dumpling Hub daily for three months before I genericized it. That usage revealed which features actually mattered.
- The domain-specific parts are thin. If 80% of your code is generic and 20% is specific to your original use case, genericization is a weekend project. If it's 50/50, you're looking at a significant rewrite.
- The tech stack is common enough. I used React + Supabase + Vite. Millions of developers know this stack. If your app is built on some obscure framework that 200 people use, your market is 200 people.
- You can explain the value in one sentence. "A business management app with invoicing, inventory, and AI" — that's clear. If your product requires a paragraph to explain, it's too complex for a digital download.
And your side project is not a good candidate if:
- It requires significant infrastructure the buyer would need to replicate (ML training pipelines, massive datasets, specialized hardware)
- The value comes from your data, not your code (a job board is worthless without listings)
- It's a thin wrapper around an API that could change or disappear
- You built it to learn a technology, not to solve a problem (learning projects make bad products)
The best digital products are the ones you built selfishly — to scratch your own itch, to help someone you love, to save yourself 20 minutes a day. The selfishness guarantees authenticity. The genericization makes it universal.
The Playbook, Summarized
If you've read this far and you're looking at one of your own repos thinking "huh, maybe" — here's the compressed version:
- Grep for hardcoded specifics. Create a config interface. Move everything to env vars and database settings. Tedious but essential.
- Build a setup wizard. If a stranger can't configure your app in under 5 minutes on first launch, it's not a product yet.
- Externalize AI context. If you have AI features, every prompt should pull business context from the DB, not from string literals in your Edge Functions.
- Add feature flags.
KitGuard+VITE_ACTIVE_KITSenv var = instant product variants from one codebase. - Write real READMEs. Not for you. For someone who has never seen your code. Include screenshots. Include troubleshooting. Be kind to your future customer.
- Automate packaging. A shell script that builds, bundles, and zips each variant. Upload to storage. Wire up Stripe + webhook for delivery. Done.
Five sessions. Two weeks of evenings. One codebase. Nine products.
Your side project graveyard might have a goldmine in it. You just need to dig up the right one and give it a haircut.
Dash Biz Suite ($59)
This is the genericized app from the post. All 7 business kits unified — invoicing, receipts, inventory, profit tracking, client intelligence, notifications, recipes. Setup wizard, AI assistant, bilingual support.
Get Dash Biz Suite — $59