From 6ea55c9dd1d3e7adbded98b3a099a87b5e410ba7 Mon Sep 17 00:00:00 2001
From: Alaister Young
Date: Wed, 5 Feb 2025 17:47:55 +0800
Subject: [PATCH 1/2] chore: static assets cdn
---
apps/web/next.config.mjs | 23 ++++++++
apps/web/package.json | 2 +-
scripts/upload-static-assets.sh | 94 +++++++++++++++++++++++++++++++++
turbo.json | 7 ++-
4 files changed, 124 insertions(+), 2 deletions(-)
create mode 100755 scripts/upload-static-assets.sh
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index ac818ad..424d890 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -5,6 +5,7 @@ import webpack from 'webpack'
/** @type {import('next').NextConfig} */
const nextConfig = {
+ assetPrefix: getAssetPrefix(),
reactStrictMode: false,
env: {
NEXT_PUBLIC_PGLITE_VERSION: await getPackageVersion('@electric-sql/pglite'),
@@ -85,3 +86,25 @@ async function getPackageVersion(module) {
const packageJson = await getPackageJson(module)
return packageJson.version
}
+
+const SUPABASE_ASSETS_URL =
+ process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
+ ? 'https://frontend-assets.supabase.green'
+ : 'https://frontend-assets.supabase.com'
+
+function getAssetPrefix() {
+ // If not force enabled, but not production env, disable CDN
+ if (process.env.FORCE_ASSET_CDN !== '1' && process.env.VERCEL_ENV !== 'production') {
+ return undefined
+ }
+
+ // Force disable CDN
+ if (process.env.FORCE_ASSET_CDN === '-1') {
+ return undefined
+ }
+
+ // @ts-ignore
+ return `${SUPABASE_ASSETS_URL}/${
+ process.env.SITE_NAME
+ }/${process.env.VERCEL_GIT_COMMIT_SHA.substring(0, 12)}`
+}
diff --git a/apps/web/package.json b/apps/web/package.json
index fe67c25..20e2206 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "npm run build:sw && next dev",
- "build": "npm run build:sw && next build",
+ "build": "npm run build:sw && next build && ./../../scripts/upload-static-assets.sh",
"build:sw": "vite build",
"start": "next start",
"lint": "next lint",
diff --git a/scripts/upload-static-assets.sh b/scripts/upload-static-assets.sh
new file mode 100755
index 0000000..f3bfc48
--- /dev/null
+++ b/scripts/upload-static-assets.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+#######
+
+# This script is used to upload static build assets (JS, CSS, ...) and public static files (public folder) to a CDN.
+# We're using Cloudflare R2 as CDN.
+# By using a CDN, we can serve static assets extremely fast while saving big time on egress costs.
+# An alternative is proxying via CF, but that comes with Orange-To-Orange issues (Cloudflare having issues with Cloudflare) and increased latency as there is a double TLS termination.
+# The script is only supposed to run on production deployments and is not run on any previews.
+
+# By using a dynamic path including the env, app and commit hash, we can ensure that there are no conflicts.
+# Static assets from previous deployments stick around for a while to ensure there are no "downtimes".
+
+# Advantages of the CDN approach we're using:
+
+# Get rid of egress costs for static assets across our apps on Vercel
+# Disable CF proxying and get around these odd timeouts issues
+# Save ~20ms or so for asset requests, as there is no additional CF proxying and we avoid terminating SSL twice
+# Always hits the CDN, gonna be super quick
+# Does not run on local or preview environments, only on staging/prod deployments
+# There are no other disadvantages - you don't have to consider it when developing locally, previews still work, everything on Vercel works as we're used to
+
+#######
+
+# If asset CDN is specifically disabled (i.e. studio self-hosted), we skip
+if [[ "$FORCE_ASSET_CDN" == "-1" ]]; then
+ echo "Skipping asset upload. Set FORCE_ASSET_CDN=1 or VERCEL_ENV=production to execute."
+ exit 0
+fi
+
+# Check for force env var or production environment
+if [[ "$FORCE_ASSET_CDN" != "1" ]] && [[ "$VERCEL_ENV" != "production" ]]; then
+ echo "Skipping asset upload. Set FORCE_ASSET_CDN=1 or VERCEL_ENV=production to execute."
+ exit 0
+fi
+
+# Set the cdnBucket variable based on NEXT_PUBLIC_ENVIRONMENT
+if [[ "$NEXT_PUBLIC_ENVIRONMENT" == "staging" ]]; then
+ BUCKET_NAME="frontend-assets-staging"
+else
+ BUCKET_NAME="frontend-assets-prod"
+fi
+
+STATIC_DIR=".next/static"
+PUBLIC_DIR="public"
+
+# Colors for output
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Install AWS CLI if not present
+if ! command -v aws &> /dev/null; then
+ echo -e "${YELLOW}Setting up AWS CLI...${NC}"
+ curl -s "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.22.35.zip" -o "awscliv2.zip"
+ unzip -q awscliv2.zip
+ export PATH=$PWD/aws/dist:$PATH
+ rm awscliv2.zip
+fi
+
+# Check if directory exists
+if [ ! -d "$STATIC_DIR" ]; then
+ echo -e "${YELLOW}Directory $STATIC_DIR not found!${NC}"
+ echo "Make sure you're running this script from your Next.js project root."
+ exit 1
+fi
+
+# Upload files with cache configuration and custom endpoint
+echo -e "${YELLOW}Uploading static files to R2...${NC}"
+aws s3 sync "$STATIC_DIR" "s3://$BUCKET_NAME/$SITE_NAME/${VERCEL_GIT_COMMIT_SHA:0:12}/_next/static" \
+ --endpoint-url "$ASSET_CDN_S3_ENDPOINT" \
+ --cache-control "public,max-age=604800,immutable" \
+ --region auto \
+ --only-show-errors
+
+# Some public files may be referenced through CSS (relative path) and therefore they would be requested via the CDN url
+# To ensure we don't run into some nasty debugging issues, we upload the public files to the CDN as well
+echo -e "${YELLOW}Uploading public files to R2...${NC}"
+aws s3 sync "$PUBLIC_DIR" "s3://$BUCKET_NAME/$SITE_NAME/${VERCEL_GIT_COMMIT_SHA:0:12}" \
+ --endpoint-url "$ASSET_CDN_S3_ENDPOINT" \
+ --cache-control "public,max-age=604800,immutable" \
+ --region auto \
+ --only-show-errors
+
+if [ $? -eq 0 ]; then
+ echo -e "${GREEN}Upload completed successfully!${NC}"
+
+ # Clean up local static files so we prevent a double upload
+ echo -e "${YELLOW}Cleaning up local static files...${NC}"
+ rm -rf "$STATIC_DIR"/*
+ echo -e "${GREEN}Local static files cleaned up${NC}"
+
+ # We still keep the public dir, as Next.js does not officially support serving the public files via CDN
+fi
\ No newline at end of file
diff --git a/turbo.json b/turbo.json
index 9a73227..d95984f 100644
--- a/turbo.json
+++ b/turbo.json
@@ -39,7 +39,12 @@
"KV_*",
"SUPABASE_*",
"LOGFLARE_*",
- "REDIRECT_LEGACY_DOMAIN"
+ "REDIRECT_LEGACY_DOMAIN",
+ "AWS_ACCESS_KEY_ID",
+ "AWS_SECRET_ACCESS_KEY",
+ "FORCE_ASSET_CDN",
+ "ASSET_CDN_S3_ENDPOINT",
+ "SITE_NAME"
],
"outputs": [".next/**", "!.next/cache/**"],
"cache": true
From 96ac4ecb121834368999e6580edd0d67af44dd17 Mon Sep 17 00:00:00 2001
From: Alaister Young
Date: Wed, 5 Feb 2025 18:15:25 +0800
Subject: [PATCH 2/2] embed assets url directly
---
apps/web/next.config.mjs | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 424d890..8190010 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -87,11 +87,6 @@ async function getPackageVersion(module) {
return packageJson.version
}
-const SUPABASE_ASSETS_URL =
- process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
- ? 'https://frontend-assets.supabase.green'
- : 'https://frontend-assets.supabase.com'
-
function getAssetPrefix() {
// If not force enabled, but not production env, disable CDN
if (process.env.FORCE_ASSET_CDN !== '1' && process.env.VERCEL_ENV !== 'production') {
@@ -104,7 +99,7 @@ function getAssetPrefix() {
}
// @ts-ignore
- return `${SUPABASE_ASSETS_URL}/${
+ return `https://frontend-assets.supabase.com/${
process.env.SITE_NAME
}/${process.env.VERCEL_GIT_COMMIT_SHA.substring(0, 12)}`
}