Photo Blog with Admin Panel
Description
EXIF-based photo blog with built-in admin panel, Vercel Postgres as CMS, and Vercel Blob for image storage.
npx boilerapp photo-blog文档
📷 EXIF Photo Blog
https://github.com/sambecker/exif-photo-blog/assets/169298/4253ea54-558a-4358-8834-89943cfbafb4
🎬 Demo
✨ Features
- Built-in auth
- Photo upload with EXIF extraction
- Organize photos by tag
- Infinite scroll
- Light/dark mode
- Automatic OG image generation
- CMD-K menu with photo search
- AI-generated text descriptions
- RSS/JSON feeds
- Support for Fujifilm recipes and film simulations
🛠️ Installation
1. Deploy to Vercel
- Click Deploy
- Add required storage (Vercel Postgres + Vercel Blob) as part of template installation
- Configure environment variable for production domain in project settings
NEXT_PUBLIC_DOMAIN(e.g., photos.domain.com—used in absolute urls and seen in navigation if no explicit nav title is set)
2. Setup Auth
- Generate auth secret and add to environment variables:
AUTH_SECRET
- Add admin user to environment variables:
ADMIN_EMAILADMIN_PASSWORD
- Trigger redeploy
- Visit project on Vercel, navigate to "Deployments" tab, click ••• button next to most recent deployment, and select "Redeploy"
3. Upload your first photo 🎉
- Visit
/admin - Sign in with credentials supplied in Step 2
- Click "Upload Photos"
- Add optional title
- Click "Create"
🔄 Receiving updates
If you don't plan to change the code, or don't mind making your updates public, consider forking this repo to easily receive future updates. If you've already set up your project on Vercel see these migration instructions.
💻 Local development
- Clone code
- Run
pnpm ito install dependencies - If necessary, install Vercel CLI and authenticate by running
vercel login - Run
vercel linkto connect CLI to your project - Run
vercel devto start dev server with Vercel-managed environment variables
See FAQ for limitations of local development
🎨 Customization
Content
NEXT_PUBLIC_META_TITLE(seen in search results and browser tab)NEXT_PUBLIC_META_DESCRIPTION(seen in search results)NEXT_PUBLIC_NAV_TITLE(seen in top-right navigation, defaults to domain when not configured)NEXT_PUBLIC_NAV_CAPTION(seen in top-right navigation, beneath title)NEXT_PUBLIC_PAGE_ABOUT(seen in grid sidebar—accepts rich formatting tags:<b>,<strong>,<i>,<em>,<u>,<br>)NEXT_PUBLIC_DOMAIN_SHARE(seen in share modals where a shorter url may be desirable)
Performance
⚠️ Enabling may result in increased project usage. See FAQ for static optimization troubleshooting hints.
NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTOS = 1enables static optimization for photo pages (p/[photoId]), i.e., renders pages at build timeNEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_OG_IMAGES = 1enables static optimization for OG images, i.e., renders images at build timeNEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORIES = 1enables static optimization for photo categories (tag/[tag],shot-on/[make]/[model], etc.), i.e., renders pages at build timeNEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORY_OG_IMAGES = 1enables static optimization for photo category (tag/[tag],shot-on/[make]/[model], etc.) OG images, i.e., renders images at build timeNEXT_PUBLIC_PRESERVE_ORIGINAL_UPLOADS = 1prevents photo uploads being compressed before storingNEXT_PUBLIC_IMAGE_QUALITY = 1-100controls the quality of large photosNEXT_PUBLIC_BLUR_DISABLED = 1prevents image blur data being stored and displayed (potentially useful for limiting Postgres usage)
AI text generation
To auto-generate text descriptions of photo:
- Setup OpenAI
- Create OpenAI account and fund it (see thread if you're having issues)
- Setup usage limits to avoid unexpected charges (recommended)
- Set
OPENAI_BASE_URLin order to use alternate OpenAI-compatible providers (experimental)
- Generate API key and store in environment variable
OPENAI_SECRET_KEY(enable Responses API write access if customizing permissions) - Add rate limiting (recommended)
- Configure auto-generated fields (optional)
- Set which text fields auto-generate when uploading a photo by storing a comma-separated list, e.g.,
AI_TEXT_AUTO_GENERATED_FIELDS = title,semantic - Accepted values:
alltitle(default)captiontags(default)semantic(default)none
- Set which text fields auto-generate when uploading a photo by storing a comma-separated list, e.g.,
Location services
To add location meta to entities like albums:
- Setup Google Places API
- Create Google Cloud project if necessary
- Select Create credentials and choose "API key"
- Choose "Restrict key" and select "Places API (new)"
- Store API key in
GOOGLE_PLACES_API_KEY - Add rate limiting (recommended)
Rate limiting
Create Upstash Redis store from storage tab of Vercel dashboard and link to your project (if required, add environment variable prefix EXIF) in order to enable rate limiting—no further configuration necessary.
Categories
NEXT_PUBLIC_CATEGORY_VISIBILITY- Comma-separated value controlling which photo sets appear in grid sidebar and CMD-K menu, and in what order. For example, you could move cameras above tags, and hide film simulations, by updating to
cameras,tags,lenses,recipes. - Accepted values:
recents(default)yearstags(default)cameras(default)lenses(default)recipes(default)films(default)focal-lengths
- Comma-separated value controlling which photo sets appear in grid sidebar and CMD-K menu, and in what order. For example, you could move cameras above tags, and hide film simulations, by updating to
NEXT_PUBLIC_HIDE_CATEGORIES_ON_MOBILE = 1prevents categories displaying on mobile grid viewNEXT_PUBLIC_HIDE_CATEGORY_IMAGE_HOVERS = 1prevents images displaying when hovering over category linksNEXT_PUBLIC_EXHAUSTIVE_SIDEBAR_CATEGORIES = 1always shows expanded sidebar contentNEXT_PUBLIC_HIDE_TAGS_WITH_ONE_PHOTO = 1to only show tags with 2 or more photos
Sorting
NEXT_PUBLIC_DEFAULT_SORT- Sets default sort on grid/full homepages
- Accepted values:
taken-at(default)taken-at-oldest-firstuploaded-atuploaded-at-oldest-first
NEXT_PUBLIC_NAV_SORT_CONTROL- Controls sort UI on grid/full homepages
- Accepted values:
nonetoggle(default)menu
- Color-based sorting (experimental)
NEXT_PUBLIC_SORT_BY_COLOR = 1enables color-based sorting (forces nav sort control to "menu," flags photos missing color data in admin dashboard)—color identification benefits greatly from AI being enabledNEXT_PUBLIC_COLOR_SORT_STARTING_HUEcontrols which colors start first (accepts a hue of 0 to 360, default: 80)NEXT_PUBLIC_COLOR_SORT_CHROMA_CUTOFFcontrols which colors are considered sufficiently vibrant (accepts a chroma of 0 to 0.37, default: 0.05):
NEXT_PUBLIC_PRIORITY_BASED_SORTING = 1takes priority field into account when sorting photos (⚠️ enabling may have performance consequences)
Display
NEXT_PUBLIC_HIDE_KEYBOARD_SHORTCUT_TOOLTIPS = 1hides keyboard shortcut hints in areas like the main nav, and previous/next photo linksNEXT_PUBLIC_HIDE_EXIF_DATA = 1hides EXIF data in photo details and OG images (potentially useful for portfolios, which don't focus on photography)NEXT_PUBLIC_HIDE_ZOOM_CONTROLS = 1hides fullscreen photo zoom controlsNEXT_PUBLIC_HIDE_TAKEN_AT_TIME = 1hides taken at time from photo metaNEXT_PUBLIC_HIDE_REPO_LINK = 1removes footer link to repo
Grid
NEXT_PUBLIC_GRID_HOMEPAGE = 1shows grid layout on homepageNEXT_PUBLIC_GRID_ASPECT_RATIO = 1.5sets aspect ratio for grid tiles (defaults to1—setting to0removes the constraint)NEXT_PUBLIC_SHOW_LARGE_THUMBNAILS = 1ensures large thumbnails on photo grid views (if not configured, density is based on aspect ratio)
Design
NEXT_PUBLIC_DEFAULT_THEME = light | darksets preferred initial theme (defaults tosystemwhen not configured)NEXT_PUBLIC_MATTE_PHOTOS = 1constrains the size of each photo, and displays a surrounding border, potentially useful for photos with tall aspect ratios (colors can be customized viaNEXT_PUBLIC_MATTE_COLOR+NEXT_PUBLIC_MATTE_COLOR_DARK)
Settings
NEXT_PUBLIC_GEO_PRIVACY = 1disables collection/display of location-based data (⚠️ re-compresses uploaded images in order to remove GPS information)NEXT_PUBLIC_ALLOW_PUBLIC_DOWNLOADS = 1enables public photo downloads for all visitors (⚠️ may result in increased bandwidth usage)NEXT_PUBLIC_SOCIAL_NETWORKS- Comma-separated list of social networks to show in share modal
- Accepted values:
x(default)threadsfacebooklinkedinallnone
NEXT_PUBLIC_SITE_FEEDS = 1enables feeds at/feed.jsonand/rss.xmlNEXT_PUBLIC_OG_TEXT_ALIGNMENT = BOTTOMkeeps OG image text bottom aligned (default is top)
Scripts & Analytics
- Web Analytics
- Open project on Vercel
- Click "Analytics" tab
- Follow "Enable Web Analytics" instructions (
@vercel/analyticsalready included)
- Speed Insights
- Open project on Vercel
- Click "Speed Insights" tab
- Follow "Enable Speed Insights" instructions (
@vercel/speed-insightsalready included)
PAGE_SCRIPT_URLS- comma-separated list of URLs to be added to the bottom of the body tag via "next/script"
- urls must begin with 'https'
- ⚠️ this will invoke arbitrary script execution on every page—use with caution
Debugging
DISABLE_DEBUG_OUTPUTS = 1- removes build identifier in
<head /> - disables
/admin/configuration/export.json
- removes build identifier in
Alternate storage providers
Only one storage adapter—Vercel Blob, Cloudflare R2, AWS S3, or MinIO—can be used at a time. Ideally, this is configured before photos are uploaded (see Issue #34 for migration considerations). If you have multiple adapters, you can set one as preferred by storing aws-s3, cloudflare-r2, minio, or vercel-blob in NEXT_PUBLIC_STORAGE_PREFERENCE. See FAQ regarding unsupported providers.
Cloudflare R2
- Setup bucket
- Create R2 bucket with default settings
- Setup CORS under bucket settings:
[{ "AllowedHeaders": ["*"], "AllowedMethods": [ "GET\
Prix
Gratuit