A mobile-first packing list web app for travel with all-items management, trip planning, and bag organization. Built with Astro, Solid.js, Tailwind CSS, Cloudflare D1, and Clerk Auth.
- Frontend: Astro.js + Solid.js + Tailwind CSS + TypeScript
- Backend: Cloudflare Workers (TypeScript)
- Database: Cloudflare D1 (SQLite at the edge)
- ORM: Drizzle ORM
- Auth: Clerk
- Deployment: Cloudflare Workers, auto-deployed on push to
main
- 📝 All items list with categories for your packing items
- 🧳 Trip-specific packing lists
- 👜 Bag organization (carry-on, checked, personal item)
- ✅ Pack/unpack tracking
- 📱 Mobile-first design with large touch targets
- 🔄 Multi-device sync via Cloudflare D1
- 🔐 Secure authentication with Clerk
- Bun installed
- Cloudflare account
- Clerk account
bun install- Go to Clerk Dashboard
- Create a new application
- Copy your Publishable Key and Secret Key
- Create a
.envfile:
cp .env.example .env- Update
.envwith your Clerk keys:
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_key_here
CLERK_SECRET_KEY=sk_test_your_key_here# Login to Cloudflare (if you haven't already)
npx wrangler login
# Create D1 database
npx wrangler d1 create packzen-dbThis will output something like:
✅ Successfully created DB 'packzen-db' in region WEUR
Created your database using D1's new storage backend.
[[d1_databases]]
binding = "DB"
database_name = "packzen-db"
database_id = "your-database-id-here"
Copy the database_id and update wrangler.jsonc:
# Apply migrations to the local D1 database
bun run db:migratebun run devThe app will be available at http://localhost:4321
# Start dev server
bun run dev
# Build for production
bun run build
# Preview production build
bun run preview
# Run tests
bun run test
# Format code with Prettier
bun run format
# Apply migrations (local)
bun run db:migrate
# Apply migrations (production)
bun run db:migrate:prod
# Open Drizzle Studio on the local database
bun run db:studioMigrations are hand-written SQL, applied in order by wrangler (and by the tests, which build their database from them):
- Add the next numbered file to
db/migrations/, e.g.0010_add_widgets.sql. Make it additive or data-preserving — never drop user data. - Mirror the change in
db/schema.ts(tables and indexes). bun run db:migratelocally, thenbun run test.- Apply to production with
bun run db:migrate:prodbefore deploying code that needs it.
├── db/ # Database schema & migrations
│ ├── schema.ts # Drizzle schema definitions
│ └── migrations/ # Hand-written SQL migrations
├── src/
│ ├── components/ # Solid.js components
│ ├── layouts/ # Astro layouts
│ ├── lib/ # Utilities
│ ├── middleware.ts # Astro middleware for auth
│ ├── pages/ # Astro routes & API endpoints
│ ├── stores/ # Solid stores
│ └── styles/ # Global CSS
├── public/ # Static assets
├── astro.config.mjs # Astro configuration
├── drizzle.config.ts # Drizzle Studio configuration
├── wrangler.jsonc # Cloudflare configuration
└── package.json
This is a Worker, not a Pages project — wrangler.jsonc is the
serving config, and it is committed and load-bearing. Pushing to main
auto-deploys via Cloudflare Workers Builds.
Only needed if Workers Builds is unavailable, or to ship without a push:
# Build the project
bun run build
# Apply pending D1 migrations to production, then deploy the Worker (and its static assets)
bun run deployNever do this from a checkout whose env files hold the live Clerk secret key:
the build inlines build-time env into the server bundle (see DEPLOYMENT.md).
The first deployment will create a new Worker in your Cloudflare account.
# Set Clerk secret key
npx wrangler secret put CLERK_SECRET_KEY
# Paste your Clerk secret key when promptedThe Clerk publishable key is public and baked in at build time, so it lives in
the committed .env.production (the build fails without it). Only secrets go
through wrangler secret put.
bun run db:migrate:prod- Minimum 44x44px touch targets
- 16px base font size (prevents iOS zoom)
- Large, clear tap targets for checkboxes
- Generous spacing (16px minimum)
- Smooth animations and transitions
- Cloudflare Workers: 100K requests/day
- Cloudflare D1: 5GB storage, 5M reads/day, 100K writes/day
- Clerk: 10K monthly active users
MIT
{ "d1_databases": [ { "binding": "DB", "database_name": "packzen-db", "database_id": "paste-your-database-id-here", }, ], }