Prototype Showcase: Turn Any App Into a Live Portfolio Demo
How to clone real projects into standalone interactive prototypes with mock authentication, dummy data, and zero backend dependencies for portfolio demos.
// table of contents (17 sections)
Try it live — The Community App demo is running at abduarrahman.com/prototype/community-app and the real app is at semesagrande.web.app. Source code on GitHub.
Live Demo
Log in with any email and password — the prototype accepts all credentials and assigns a role. Explore both the Pengurus (admin) and Warga (member) views:
The Portfolio Problem
Here’s the dilemma every developer faces when building a portfolio:
- Your best work is proprietary — Client projects, employer codebases, internal tools. You can’t share the source, and you definitely can’t deploy it.
- Screenshots are dead — A gallery of static images tells someone what an app looks like, not how it works. Navigation flows, form interactions, state transitions — these are invisible in screenshots.
- Videos are better but passive — Screen recordings show the app in action, but the viewer can’t explore at their own pace. They’re stuck on your timeline.
- Side projects are a time sink — Building portfolio-only projects from scratch means less time for real work.
What if you could take the actual UI/UX from a proprietary project, strip out the backend and real data, and deploy it as a fully interactive demo? That’s the Prototype Showcase Pattern.
The Pattern
Real App → Clone → Rebrand → Mock → Deploy → Showcase
- Clone — Copy the frontend code into a new project
- Rebrand — Remove proprietary names, logos, and identifying details
- Mock — Replace real services (Firebase, API calls) with client-side mocks
- Seed — Populate with realistic dummy data
- Deploy — Build as a static SPA, host on any file server
- Showcase — Embed as iframe on portfolio, link from project page
The result is a zero-backend, fully interactive demo that anyone can click through without credentials, API keys, or setup.
Mock Firebase Deep-Dive
The most critical part of the pattern is mocking the backend services. For my community management app, the real version uses Firebase Authentication and Firestore. Here’s how I replaced them:
Auth Mocking with Observer Pattern
Firebase auth uses an observer pattern — you call onAuthStateChanged(callback) and get notified when the user changes. The mock replicates this exactly:
// mocks/firebase.js
const observers = new Set();
let currentUser = null;
export function onAuthStateChanged(callback) {
observers.add(callback);
callback(currentUser);
return () => observers.delete(callback);
}
function notifyObservers(user) {
currentUser = user;
observers.forEach(cb => cb(user));
}
export async function signInWithEmailAndPassword(email, password) {
// Accept ANY credentials for the prototype
const role = determineRole(email);
const user = { uid: generateId(), email, displayName: email.split('@')[0] };
notifyObservers(user);
return { user };
}
Components that use onAuthStateChanged don’t know (or care) whether they’re talking to real Firebase or the mock. This is the power of programming to an interface.
Firestore Mocking with localStorage
Real Firestore is async and document-based. The mock uses localStorage as the persistence layer:
const STORAGE_KEY = 'mock_firestore';
function getCollection(collectionName) {
const data = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
return data[collectionName] || [];
}
function setCollection(collectionName, documents) {
const data = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
data[collectionName] = documents;
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}
This means data survives page refreshes — the prototype feels like a real app because state persists across navigation.
Dummy Data Seeding
On first load, the prototype checks if localStorage is empty and seeds it with realistic data:
function seedData() {
if (localStorage.getItem(STORAGE_KEY)) return; // Already seeded
const warga = [
{ id: 'w001', nama: 'Budi Santoso', alamat: 'Jl. Melati No. 5', rt: '003', rw: '001' },
{ id: 'w002', nama: 'Siti Aminah', alamat: 'Jl. Melati No. 12', rt: '003', rw: '001' },
{ id: 'w003', nama: 'Ahmad Fauzi', alamat: 'Jl. Dahlia No. 8', rt: '004', rw: '001' },
// ... 20+ more realistic entries
];
const kas = [
{ id: 'k001', jenis: 'iuran_bulanan', jumlah: 25000, tanggal: '2026-04-01', wargaId: 'w001', status: 'lunas' },
{ id: 'k002', jenis: 'iuran_bulanan', jumlah: 25000, tanggal: '2026-04-01', wargaId: 'w002', status: 'belum_bayar' },
// ... with various states for demo purposes
];
setCollection('warga', warga);
setCollection('kas', kas);
}
The key is realism — “Budi Santoso, Jl. Melati No. 5” is infinitely more convincing than “Test User 1, Address 1”. The data tells a story.
The Community App Example
The prototype showcases a residential community management system (Sistem Pengelolaan Rukun Tetangga) with two distinct views:
Pengurus (Admin) View
- Dashboard with kas summary and pending items
- Kas management — create dues, record payments, track outstanding
- Audit workflow — submit entries for approval, approve with notes
- Warga directory — full resident list with contact info
- Export to Excel — download kas data as
.xlsx
Warga (Member) View
- Personal payment status — see what’s paid and what’s owed
- Community documents — view announcements and files
- Paguyuban info — RT/RW structure and community hierarchy
The role is determined at login time — use any email containing “pengurus” for admin access, or any other email for the member view.
Deployment: Subdirectory Hosting
The prototype lives at /prototype/community-app/ on my portfolio site, not on its own domain. This requires two configuration changes:
Vite Base Path
// vite.config.js
export default defineConfig({
plugins: [react()],
base: '/prototype/community-app/',
});
This makes all asset references relative to the subdirectory: <script src="/prototype/community-app/assets/index.js">.
React Router Basename
<Router basename="/prototype/community-app">
<Routes>
<Route path="/" element={<Tentang />} />
<Route path="/login" element={<Login />} />
{/* ... */}
</Routes>
</Router>
Without basename, React Router would try to match /login instead of /prototype/community-app/login.
Nginx SPA Fallback
Single-page apps need all routes to fall back to index.html so client-side routing works:
location /prototype/ {
alias /var/www/abduarrahmancom/dist/prototype/;
try_files $uri $uri/ /prototype/community-app/index.html;
}
This ensures that navigating directly to /prototype/community-app/pengurus serves the SPA’s index.html instead of a 404.
What Makes a Good Prototype
Not all prototypes are created equal. Here’s what separates a live portfolio demo from a dead prototype:
| Factor | Dead Prototype | Live Showcase |
|---|---|---|
| Data | ”Lorem ipsum” test data | Realistic, contextual data |
| Auth | Hardcoded single user | Mock auth accepting any credentials |
| Navigation | Only homepage works | All routes functional |
| State | Resets on every refresh | Persists across navigation |
| Deployment | localhost only | Publicly accessible URL |
| Embedding | No | Iframe-ready for portfolio |
The goal is to make the prototype indistinguishable from a real app during a 5-minute click-through. If someone has to ask “is this broken or is it a demo?” — you’ve failed.
The /proto:clone Automation
I automated the entire clone-and-mock process into a Claude Code command: /proto:clone. Given a GitHub repository, it:
- Clones the repo into a new prototype project
- Identifies backend dependencies (Firebase, API calls, databases)
- Generates mock implementations matching the real interfaces
- Creates realistic seed data based on the app’s domain
- Configures Vite for subdirectory deployment
- Sets up React Router with proper basename
What used to take a full day of manual work now runs in about 15 minutes. The command produces a standalone project that builds to static files and deploys anywhere.
Takeaways
- Live demos beat everything — A recruiter who clicks through your prototype understands your work 10x better than one who reads bullet points
- Mock the interface, not the implementation — Components should use the same API shape as the real backend. The mock is a drop-in replacement
- Data realism is worth the effort — Spend time crafting good seed data. It’s the difference between “nice UI” and “wow, this feels real”
- Subdirectory hosting is underused — You don’t need a separate domain for every demo.
/prototype/project-name/works great - Automate the pattern — Once you’ve done it manually, codify it. The
/proto:clonecommand turns any project into a showcase in minutes
If you’re building a developer portfolio, stop screenshotting. Start prototyping.
The prototype showcases my Semesa Grande community management app — a full-stack PWA with role-based dashboards, kas payment tracking, and push notifications for residential communities. For another approach to building impressive portfolio demos, see my Event Photo Platform with OCR project.
You might also like
Web Components 2026: Building Framework-Agnostic UI Libraries
Learn how to build reusable Web Components that work across React, Vue, Angular, and vanilla JS. Create framework-agnostic UI libraries with Shadow DOM and Custom Elements.
API Gateway Patterns: The Front Door to Your Microservices
Master API Gateway patterns for microservices architecture. Learn request routing, authentication, rate limiting, and service mesh integration with TypeScript examples.
Workflow Orchestration 2026: Temporal vs Inngest vs Trigger.dev
Compare leading workflow orchestration platforms for modern applications. Learn when to use Temporal, Inngest, or Trigger.dev for background jobs, event-driven architectures, and durable workflows.
More Posts
API Gateway Patterns: The Front Door to Your Microservices
Web Components 2026: Building Framework-Agnostic UI Libraries
Building Autonomous AI Workflows with LangGraph: A Practical Guide
Building Type-Safe APIs with tRPC in 2026: Full-Stack TypeScript Without Schemas
Database Connection Pooling: Patterns for High-Performance Applications
Prompt Caching: Reduce LLM Costs by 90% with Smart Context Management
Enjoyed This Post?
Want to discuss the topic, have questions, or looking to collaborate on something similar? Drop a comment below or reach out directly.
