diff --git a/README.md b/README.md
index 262260b5..c4dfeed7 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,42 @@ Installation
- `NEXT_PUBLIC_HIDE_REPO_LINK = 1` removes footer link to repo
- `NEXT_PUBLIC_HIDE_FILM_SIMULATIONS = 1` prevents Fujifilm simulations showing up in `/grid` sidebar
+### Setup alternate storage
+
+#### AWS S3
+
+1. [Create bucket](https://s3.console.aws.amazon.com/s3) with "Block all public access" turned off
+ - Setup CORS:
+ ```
+ [{
+ "AllowedHeaders": [],
+ "AllowedMethods": [
+ "GET"
+ ],
+ "AllowedOrigins": [
+ "http://localhost:*",
+ "{PRODUCTION_DOMAIN}",
+ "https://*${VERCEL_PROJECT}.vercel.app"
+ ],
+ "ExposeHeaders": []
+ }]
+ ```
+ - Store configuration
+ - `NEXT_PUBLIC_S3_BUCKET`
+ - `NEXT_PUBLIC_S3_REGION`
+2. [Create IAM policy](https://console.aws.amazon.com/iam/home#/policies) for client uploads (JSON editor recommended)
+ - Action: `s3:PutObject`
+ - Resource: `arn:aws:s3:::{BUCKET_NAME}/uploads/*`
+3. [Create IAM policy](https://console.aws.amazon.com/iam/home#/policies) for admin actions (JSON editor recommended)
+ - Action: `s3:PutObject`, `s3:GetObject`, `s3:ListBucket`, `s3:DeleteObject`
+ - Resource: `arn:aws:s3:::{BUCKET_NAME}`, `arn:aws:s3:::{BUCKET_NAME}/*`
+4. [Create IAM user](https://console.aws.amazon.com/iam/home#/users) for upload policy (by choosing "Attach policies directly"), create access key under "Security credentials," choose "Application running outside AWS," and store credentials
+ - `NEXT_PUBLIC_S3_UPLOAD_ACCESS_KEY`
+ - `NEXT_PUBLIC_S3_UPLOAD_SECRET_ACCESS_KEY`
+5. [Create IAM user](https://console.aws.amazon.com/iam/home#/users), for admin policy (by choosing "Attach policies directly"), , create access key under "Security credentials," choose "Application running outside AWS," and store credentials (_ensure admin environment variables are not prefixed with `NEXT_PUBLIC`_)
+ - `S3_ADMIN_ACCESS_KEY`
+ - `S3_ADMIN_SECRET_ACCESS_KEY`
+
FAQ
-
Q: My images/content have fallen out of sync with my database and/or my production site no longer matches local development. What do I do?
diff --git a/next.config.js b/next.config.js
index 1b9df8e9..377b4673 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,24 +1,40 @@
-/** @type {import('next').NextConfig} */
-
-const STORE_ID = process.env.BLOB_READ_WRITE_TOKEN?.match(
+const VERCEL_BLOB_STORE_ID = process.env.BLOB_READ_WRITE_TOKEN?.match(
/^vercel_blob_rw_([a-z0-9]+)_[a-z0-9]+$/i,
)?.[1].toLowerCase();
+const VERCEL_BLOB_HOSTNAME = VERCEL_BLOB_STORE_ID
+ ? `${VERCEL_BLOB_STORE_ID}.public.blob.vercel-storage.com`
+ : undefined;
+
+const AWS_S3_HOSTNAME =
+ process.env.NEXT_PUBLIC_S3_BUCKET &&
+ process.env.NEXT_PUBLIC_S3_REGION
+ // eslint-disable-next-line max-len
+ ? `${process.env.NEXT_PUBLIC_S3_BUCKET}.s3.${process.env.NEXT_PUBLIC_S3_REGION}.amazonaws.com`
+ : undefined;
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ images: {
+ imageSizes: [200],
+ remotePatterns: []
+ .concat(VERCEL_BLOB_HOSTNAME ? {
+ protocol: 'https',
+ hostname: VERCEL_BLOB_HOSTNAME,
+ port: '',
+ pathname: '/**',
+ } : [])
+ .concat(AWS_S3_HOSTNAME ? {
+ protocol: 'https',
+ hostname: AWS_S3_HOSTNAME,
+ port: '',
+ pathname: '/**',
+ } : []),
+ minimumCacheTTL: 31536000,
+ },
+};
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
-const nextConfig = {
- images: {
- imageSizes: [200],
- remotePatterns: [{
- protocol: 'https',
- hostname: `${STORE_ID}.public.blob.vercel-storage.com`,
- port: '',
- pathname: '/**',
- }],
- minimumCacheTTL: 31536000,
- },
-};
-
module.exports = withBundleAnalyzer(nextConfig);
diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx
index 593513d5..0832e998 100644
--- a/src/site/SiteChecklistClient.tsx
+++ b/src/site/SiteChecklistClient.tsx
@@ -121,16 +121,30 @@ export default function SiteChecklistClient({
and connect to project
- {renderLink(
- 'https://vercel.com/docs/storage/vercel-blob/quickstart',
- 'Create Vercel Blob store',
- )}
- {' '}
- and connect to project
+
+ -
+ Vercel Blob:
+ {' '}
+ {renderLink(
+ 'https://vercel.com/docs/storage/vercel-blob/quickstart',
+ 'create store',
+ )}
+ {' '}
+ and connect to project
+
+ -
+ AWS S3:
+ {' '}
+ {renderLink(
+ 'https://github.com/sambecker/exif-photo-blog#aws-s3',
+ 'create/configure bucket',
+ )}
+
+
0;
+const hasAwsS3Storage =
+ (process.env.NEXT_PUBLIC_S3_BUCKET ?? '').length > 0 &&
+ (process.env.NEXT_PUBLIC_S3_REGION ?? '').length > 0 &&
+ (process.env.NEXT_PUBLIC_S3_UPLOAD_ACCESS_ID ?? '').length > 0 &&
+ (process.env.NEXT_PUBLIC_S3_UPLOAD_SECRET ?? '').length > 0 &&
+ (process.env.S3_ADMIN_ACCESS_ID ?? '').length > 0 &&
+ (process.env.S3_ADMIN_ACCESS_SECRET ?? '').length > 0;
+
+
+// SETTINGS
+
export const PRO_MODE_ENABLED = process.env.NEXT_PUBLIC_PRO_MODE === '1';
export const PUBLIC_API_ENABLED = process.env.NEXT_PUBLIC_PUBLIC_API === '1';
export const SHOW_REPO_LINK = process.env.NEXT_PUBLIC_HIDE_REPO_LINK !== '1';
@@ -38,7 +54,7 @@ export const OG_TEXT_BOTTOM_ALIGNMENT =
export const CONFIG_CHECKLIST_STATUS = {
hasPostgres: (process.env.POSTGRES_HOST ?? '').length > 0,
- hasBlob: (process.env.BLOB_READ_WRITE_TOKEN ?? '').length > 0,
+ hasBlob: hasVercelBlob || hasAwsS3Storage,
hasAuth: (process.env.AUTH_SECRET ?? '').length > 0,
hasAdminUser: (
(process.env.ADMIN_EMAIL ?? '').length > 0 &&