A free personal photo gallery deployable on Cloudflare, with AI image analysis and browser-side image compression
- 🆓 Free Cloudflare Deployment - Zero-cost hosting on Cloudflare Workers with generous free tier
- 🧠 AI-Powered Image Intelligence - Integration with OpenAI and Gemini for semantic analysis and intelligent image descriptions
- 🖼️ Smart Image Processing - Browser-side compression supporting JPEG, WebP, and AVIF formats with automatic thumbnail generation
- 💾 Edge-Native Storage - Cloudflare R2 object storage with D1 database for optimal performance and global edge deployment
- 📊 Complete EXIF Management - Full extraction and display of image metadata including camera settings, location data, and timestamps
- 🏷️ Flexible Tagging System - Organize photos with custom tags and filter by categories
- 📑 Sorting & Pagination - Sort photos and smooth pagination
- 🎨 Modern User Experience - Responsive design with smooth view transitions and beautiful UI components
- 🔐 Secure Admin Panel - Built-in authentication system for secure photo management and uploads
# Install pnpm (if not already installed)
corepack enable pnpm
# Clone the repository
git clone https://github.com/wiidede/exif-gallery-nuxt.git
cd exif-gallery-nuxt
# Install dependencies
pnpm install
# Start development server
pnpm devVisit http://localhost:3000 to see the application.
- Framework: Nuxt 4 - The Intuitive Vue Framework
- Edge Platform: NuxtHub - Build fullstack applications on the edge
- Database: D1 - SQLite at the edge
- Storage: R2 - S3-compatible object storage
- Styling: UnoCSS - The instant on-demand atomic CSS engine
- UI Components: shadcn-vue + inspira-ui
- State Management: Pinia
- Validation: vee-validate + Zod
- AI: OpenAI + Google Gemini
- Code Quality: TypeScript + ESLint
This project is designed for deployment on Cloudflare Workers with NuxtHub.
-
Create D1 Database
- Navigate to Storage & Databases → D1 SQL Database in Cloudflare Dashboard
- Create a database named
exif-gallery-nuxtand note the Database ID
-
Create R2 Bucket
- Navigate to Storage & Databases → R2 Object Storage
- Create a bucket and note the bucket name
Update wrangler.jsonc with your Cloudflare resource IDs:
Important: Cloudflare D1 database cannot be connected during build, so migrations are not automatically applied. You must manually run migrations to create the table structure.
Using GitHub Actions (Recommended, Automated)
The project includes a .github/workflows/migrate.yml file. You can:
-
Add the following secrets in your GitHub repository settings:
CLOUDFLARE_ACCOUNT_ID- Your Cloudflare account ID (visible in Cloudflare Dashboard)CLOUDFLARE_API_TOKEN- API token with D1 edit permissions (create at Cloudflare Dashboard → My Profile → API Tokens)
-
Push code to the
mainbranch, or manually trigger theDatabase Migrationworkflow in GitHub Actions
Note
This project adopts a separated migration management strategy:
- Local Development: Migrations are automatically managed by NuxtHub and recorded in the
_hub_migrationstable. - Cloud Deployment: Migrations are managed via GitHub Actions using Wrangler, also recorded in the
_hub_migrationstable, but with an additional.sqlfile extension compared to NuxtHub migrations. - Note: Do not manually run Wrangler migration commands during local development, as those files lack the
.sqlsuffix.
- Go to Workers & Pages → Create application → Connect to Git
- Select your forked repository
- Configure build settings:
- Build command:
pnpm run build - Deploy command:
npx wrangler deploy
- Build command:
- Add environment variables:
NUXT_SESSION_PASSWORD- Generate a secure random string (at least 32 characters)NUXT_ADMIN_PASSWORD- Set your admin panel password
- Click Deploy
NuxtHub will automatically configure D1 and R2 bindings based on wrangler.jsonc.
# Build for production
pnpm run build
# Deploy to Cloudflare Workers
npx wrangler deployConnect to your remote Cloudflare resources locally:
pnpm dev --remoteFor users who previously deployed using NuxtHub Admin:
-
Update your fork to get the latest changes:
-
Get existing resources from your NuxtHub project:
- D1 database ID
- R2 bucket name
-
Update
wrangler.jsoncwith your existing resources:{ "d1_databases": [{ "binding": "DB", "database_id": "YOUR_EXISTING_DATABASE_ID" }], "r2_buckets": [{ "binding": "BLOB", "bucket_name": "YOUR_EXISTING_BUCKET_NAME" }] }Commit and push this change.
-
Create new Worker by following steps 2-3 in the deployment section above
-
Configure environment variables from your old project
-
Deploy - your data remains in the same D1 database and R2 bucket
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_ADMIN_PASSWORD |
Yes | admin |
Admin panel access password |
NUXT_SESSION_PASSWORD |
Yes | -- | Session encryption key(at least 32 characters) |
NUXT_PUBLIC_TITLE |
No | Exif Gallery Nuxt |
Application title |
NUXT_PUBLIC_DESCRIPTION |
No | A full-stack photo album solution that integrates AI intelligent processing, browser image compression, and other functions |
Application description |
NUXT_PUBLIC_DISABLE_3D_CARD_DEFAULT |
No | false |
Whether to disable 3D card effect by default (set to true to disable) |
exif-gallery-nuxt/
├── app/ # Frontend application
│ ├── components/ # Vue components
│ ├── composables/ # Vue composables
│ ├── pages/ # Application pages
│ ├── stores/ # Pinia stores
│ ├── utils/ # Utility functions
│ └── workers/ # Web Workers
├── server/ # Backend API
│ ├── api/ # API routes
│ ├── db/ # Database schema
│ └── utils/ # Server utilities
└── types/ # TypeScript definitions
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- exif-photo-blog - Inspiration for EXIF handling
- nuxt-image-gallery - Gallery implementation reference
- NuxtHub - Edge deployment platform
- shadcn-vue - Beautiful UI components
- inspira-ui - Animated UI components
Made with ❤️ by wiidede

{ "d1_databases": [ { "binding": "DB", "database_name": "exif-gallery-nuxt", "database_id": "YOUR_DATABASE_ID", "migrations_dir": "server/db/migrations/sqlite", "migrations_table": "_hub_migrations" } ], "r2_buckets": [ { "binding": "BLOB", "bucket_name": "YOUR_BUCKET_NAME" } ] }