Use R2 for public image assets
This commit is contained in:
@@ -27,3 +27,8 @@ AUTH_URL=http://localhost:3000
|
||||
# https://console.cloud.google.com/apis/credentials
|
||||
AUTH_GOOGLE_ID=
|
||||
AUTH_GOOGLE_SECRET=
|
||||
|
||||
# Cloudflare R2 public image origin
|
||||
# Use a custom domain or R2 public development URL with no trailing slash.
|
||||
# Upload site assets under /images, for example /images/hero-rv-sunset.jpg.
|
||||
NEXT_PUBLIC_R2_PUBLIC_URL=
|
||||
|
||||
@@ -1,8 +1,40 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
function getR2PublicUrl(): URL | null {
|
||||
const publicUrl = process.env.NEXT_PUBLIC_R2_PUBLIC_URL?.trim();
|
||||
|
||||
if (!publicUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(publicUrl);
|
||||
|
||||
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return url;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const r2PublicUrl = getR2PublicUrl();
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
images: {
|
||||
remotePatterns: [],
|
||||
remotePatterns: r2PublicUrl
|
||||
? [
|
||||
{
|
||||
protocol: r2PublicUrl.protocol.replace(":", "") as "http" | "https",
|
||||
hostname: r2PublicUrl.hostname,
|
||||
port: r2PublicUrl.port,
|
||||
pathname: "/**",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
unoptimized: Boolean(r2PublicUrl),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Image from "next/image";
|
||||
import SectionWrapper from "@/components/ui/SectionWrapper";
|
||||
import Button from "@/components/ui/Button";
|
||||
import { assetUrl } from "@/lib/assets";
|
||||
|
||||
export default function FounderSection() {
|
||||
return (
|
||||
@@ -10,7 +11,7 @@ export default function FounderSection() {
|
||||
<div className="relative">
|
||||
<div className="relative rounded-2xl overflow-hidden aspect-[4/5] bg-slate-100 shadow-xl">
|
||||
<Image
|
||||
src="/images/david.jpg"
|
||||
src={assetUrl("/images/david.jpg")}
|
||||
alt="David, Founder of TechSolve Travel, working from his RV with a Starlink and Peplink setup"
|
||||
fill
|
||||
className="object-cover object-top"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import Image from "next/image";
|
||||
import Button from "@/components/ui/Button";
|
||||
import { assetUrl } from "@/lib/assets";
|
||||
|
||||
export default function HeroSection() {
|
||||
return (
|
||||
<section className="relative min-h-[90vh] flex items-center">
|
||||
{/* Background image */}
|
||||
<Image
|
||||
src="/images/hero-rv-sunset.jpg"
|
||||
src={assetUrl("/images/hero-rv-sunset.jpg")}
|
||||
alt="RV parked at sunset with Starlink dish and enterprise networking hardware"
|
||||
fill
|
||||
priority
|
||||
|
||||
33
src/lib/assets.ts
Normal file
33
src/lib/assets.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
const assetBaseUrl = getAssetBaseUrl();
|
||||
|
||||
function getAssetBaseUrl(): string {
|
||||
const rawUrl = process.env.NEXT_PUBLIC_R2_PUBLIC_URL?.trim();
|
||||
|
||||
if (!rawUrl) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(rawUrl);
|
||||
|
||||
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
||||
return "";
|
||||
}
|
||||
|
||||
url.search = "";
|
||||
url.hash = "";
|
||||
|
||||
return url.href.replace(/\/$/, "");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function assetUrl(path: string): string {
|
||||
if (!assetBaseUrl) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
||||
return `${assetBaseUrl}${normalizedPath}`;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PricingTier, ProcessStep, TrustLogo } from "@/types";
|
||||
import { assetUrl } from "@/lib/assets";
|
||||
|
||||
export const PROCESS_STEPS: ProcessStep[] = [
|
||||
{
|
||||
@@ -98,34 +99,39 @@ export const PRICING_TIERS: PricingTier[] = [
|
||||
];
|
||||
|
||||
export const TRUST_LOGOS: TrustLogo[] = [
|
||||
{ name: "Peplink", src: "/images/logo-peplink.svg", width: 120, height: 40 },
|
||||
{
|
||||
name: "Peplink",
|
||||
src: assetUrl("/images/logo-peplink.svg"),
|
||||
width: 120,
|
||||
height: 40,
|
||||
},
|
||||
{
|
||||
name: "GL.iNet",
|
||||
src: "/images/logo-gliinet.svg",
|
||||
src: assetUrl("/images/logo-gliinet.svg"),
|
||||
width: 100,
|
||||
height: 40,
|
||||
},
|
||||
{
|
||||
name: "Ubiquiti UniFi",
|
||||
src: "/images/logo-ubiquiti-unifi.svg",
|
||||
src: assetUrl("/images/logo-ubiquiti-unifi.svg"),
|
||||
width: 110,
|
||||
height: 40,
|
||||
},
|
||||
{
|
||||
name: "WireGuard",
|
||||
src: "/images/logo-wireguard.svg",
|
||||
src: assetUrl("/images/logo-wireguard.svg"),
|
||||
width: 120,
|
||||
height: 40,
|
||||
},
|
||||
{
|
||||
name: "Fortinet",
|
||||
src: "/images/logo-fortinet.svg",
|
||||
src: assetUrl("/images/logo-fortinet.svg"),
|
||||
width: 110,
|
||||
height: 40,
|
||||
},
|
||||
{
|
||||
name: "Starlink",
|
||||
src: "/images/logo-starlink.svg",
|
||||
src: assetUrl("/images/logo-starlink.svg"),
|
||||
width: 110,
|
||||
height: 40,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user