A Google Drive clone with real per-account isolation. Sign in with Google and everything you upload is yours alone — no shared folder, no other account can see or touch your files.
🔗 Live demo: https://google-drive-clone-inky-iota.vercel.app
Most "Google Drive clone" tutorial projects skip the part that actually makes a drive personal: real per-account isolation. This one doesn't — every file is scoped to the Google account that uploaded it, enforced by Firestore security rules (not just hidden in the UI), so signing in with a different account gets you an empty drive, not someone else's files.
- Google Sign-In — the only way in, no anonymous access
- Upload files straight to your own private space
- Live file list backed by Firestore (
onSnapshot), updates in real time as uploads land - Search your own files by name
- Download any file you've uploaded
- 100MB storage quota per account, with a live usage bar in the sidebar (turns red past 90% used) and an upfront error if an upload would exceed it — checked client-side before the upload even starts, not after it fails
This started as a typical "Google Drive clone" tutorial project — and had the usual gaps that come with that: Google Sign-In was fully commented out, so every visitor shared one global file list, and uploads silently failed forever with no error shown (Firebase Storage was rejecting them since nobody was actually authenticated). Getting this into something worth linking from a resume meant closing those gaps for real, not just cosmetically:
- Fixed the upload bug and added real accounts in the same change — restored Google Sign-In and scoped every file to the signed-in user's UID, both in the data model and in Firestore's security rules (server- enforced, not just filtered client-side). That was also the actual fix for uploads never completing.
- Redesigned the UI to match real Google Drive's look and feel — white surfaces, Drive's blue, the familiar "+ New" button and sidebar nav — rather than a generic reskin.
- Migrated file storage from Firebase Storage to Cloudinary after hitting Firebase's policy requiring the paid Blaze plan to manage Storage. Cloudinary's free tier does the same job (client-side unsigned uploads, no backend needed) without requiring a card.
- Added a per-account storage quota (100MB) with a real usage bar, so the "account" concept means something concrete, not just "your own empty folder."
See firestore.rules for the actual access-control logic and the Data
model & security section below for the one real tradeoff that came out of
the Cloudinary migration.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, Tailwind CSS, MUI icons |
| Auth + file metadata | Firebase (Authentication, Firestore) |
| File storage | Cloudinary (unsigned client-side uploads) |
| Hosting | Vercel |
There's no backend server in this project — the browser talks directly to Firebase and Cloudinary.
Firebase Storage isn't used here: as of late 2024, Firebase requires the paid Blaze plan to manage Storage, even for the free-tier quota. Cloudinary has a genuinely free tier (25GB storage/bandwidth, no card required), so that's where the actual file bytes live; Firestore still holds each file's metadata (name, size, uploader, timestamp, and the Cloudinary URL).
Every file's Firestore document carries a uid field (the uploader's
Firebase Auth UID) — enforced server-side by firestore.rules, not just
filtered client-side: a myfiles document can only be read or written by
the UID it belongs to.
One honest caveat: Cloudinary's unsigned free tier doesn't support per-user access rules the way Firebase Storage did — an uploaded file's URL is technically public if someone has (or guesses) it. In practice this is fine for this app: the URL is only ever revealed to the uploader, through their own Firestore-gated file list, and Cloudinary's auto-generated IDs aren't guessable. But it's "private by not being discoverable," not cryptographically enforced access control like the Firestore side is. Worth knowing if you extend this into something handling actually sensitive files.
If you fork this and stand up your own Firebase project, deploy the Firestore rules with the Firebase CLI:
firebase deploy --only firestore:rules(or paste firestore.rules's contents into Firebase Console → Firestore →
Rules directly).
Prerequisites: Node.js ≥ 18, a Firebase project with Authentication (Google provider enabled) and Firestore turned on, and a free Cloudinary account.
git clone https://github.com/mkhalidh/Google_Drive.git
cd Google_Drive
npm installCreate .env from .env.example and fill in your Firebase project's web
config (Firebase Console → Project Settings → General → Your apps) plus
your Cloudinary cloud name and an unsigned upload preset (Cloudinary
dashboard → Settings → Upload → Upload presets → Signing Mode: Unsigned):
VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
VITE_FIREBASE_MEASUREMENT_ID=
VITE_CLOUDINARY_CLOUD_NAME=
VITE_CLOUDINARY_UPLOAD_PRESET=Then:
npm run dev # http://localhost:5173Deployed to Vercel, with the env vars above set as Vercel environment variables (Project Settings → Environment Variables). Also add the deployed domain under Firebase Console → Authentication → Settings → Authorized domains, or Google Sign-In will reject it. CI/CD runs via GitHub Actions:
.github/workflows/ci.yml— lint + build on every push/PR.github/workflows/deploy.yml— deploys to Vercel on every push tomain(needsVERCEL_TOKEN,VERCEL_ORG_ID,VERCEL_PROJECT_IDrepo secrets)
Google_Drive/
├── src/
│ ├── App.jsx # Auth state machine (sign-in/out), top-level layout
│ ├── Header.jsx # Search, account menu, sign-out
│ ├── Sidebar.jsx # Upload flow (to Cloudinary), 100MB quota + usage bar
│ ├── Data.jsx # Per-user file list
│ ├── firebase.js # Firebase init (reads VITE_FIREBASE_* env vars)
│ └── css/ # Component styles
├── firestore.rules # Per-user Firestore access rules
└── .github/workflows/ # CI/CD
MIT