fix(deploy+docker+frontend): enforce same-origin via Nginx, disable Next Image optimization in Docker, allow Socket.IO polling fallback, and improve health checks and access info
- generate-config.sh: add --with-nginx flag handling; when enabled, set NEXT_PUBLIC_API_URL empty to use same-origin /api and /socket.io; add BACKEND_INTERNAL_URL for SSR/internal fetch; adjust lan-tls HTTPS (8443) and TLS generation policy - deploy.sh: show only valid access URLs when Nginx is enabled (gateway URLs), avoid misleading :3002/:3001 entries - frontend (env/webrtc): return mutable transports [websocket,polling]; use empty signaling server for same-origin; comments in English - frontend (next.config): support NEXT_IMAGE_UNOPTIMIZED to turn off image optimization in Docker - frontend (health): prefer BACKEND_INTERNAL_URL for internal health checks, fallback to public URL/localhost - docker-compose + Dockerfile(frontend): pass NEXT_IMAGE_UNOPTIMIZED and BACKEND_INTERNAL_URL envs
This commit is contained in:
@@ -582,8 +582,13 @@ show_deployment_info() {
|
|||||||
echo " API: http://$domain_name:${backend_port:-3001}"
|
echo " API: http://$domain_name:${backend_port:-3001}"
|
||||||
fi
|
fi
|
||||||
elif [[ -n "$public_ip" ]]; then
|
elif [[ -n "$public_ip" ]]; then
|
||||||
echo " Public access: http://$public_ip:${frontend_port:-3002}"
|
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||||
echo " API: http://$public_ip:${backend_port:-3001}"
|
echo " Public access: http://$public_ip"
|
||||||
|
echo " API: http://$public_ip"
|
||||||
|
else
|
||||||
|
echo " Public access: http://$public_ip:${frontend_port:-3002}"
|
||||||
|
echo " API: http://$public_ip:${backend_port:-3001}"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# Fallback: show LAN and localhost if public IP is unavailable
|
# Fallback: show LAN and localhost if public IP is unavailable
|
||||||
echo " Frontend: http://localhost:${frontend_port:-3002}"
|
echo " Frontend: http://localhost:${frontend_port:-3002}"
|
||||||
@@ -591,13 +596,24 @@ show_deployment_info() {
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Private/basic: localhost + LAN
|
# Private/basic: localhost + LAN
|
||||||
echo " Frontend: http://localhost:${frontend_port:-3002}"
|
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||||
echo " Backend API: http://localhost:${backend_port:-3001}"
|
# When Nginx is enabled and frontend uses same-origin API, prefer the gateway as the primary entry
|
||||||
|
echo " Frontend: http://localhost"
|
||||||
|
echo " API: http://localhost"
|
||||||
|
else
|
||||||
|
echo " Frontend: http://localhost:${frontend_port:-3002}"
|
||||||
|
echo " Backend API: http://localhost:${backend_port:-3001}"
|
||||||
|
fi
|
||||||
if [[ -n "$local_ip" ]] && [[ "$local_ip" != "127.0.0.1" ]]; then
|
if [[ -n "$local_ip" ]] && [[ "$local_ip" != "127.0.0.1" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BLUE}🌐 LAN Access:${NC}"
|
echo -e "${BLUE}🌐 LAN Access:${NC}"
|
||||||
echo " Frontend: http://$local_ip:${frontend_port:-3002}"
|
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||||
echo " Backend API: http://$local_ip:${backend_port:-3001}"
|
echo " Frontend: http://$local_ip"
|
||||||
|
echo " API: http://$local_ip"
|
||||||
|
else
|
||||||
|
echo " Frontend: http://$local_ip:${frontend_port:-3002}"
|
||||||
|
echo " Backend API: http://$local_ip:${backend_port:-3001}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -62,11 +62,14 @@ services:
|
|||||||
- NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
- NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
||||||
- NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
- NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
||||||
- NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
- NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
||||||
|
- NEXT_IMAGE_UNOPTIMIZED=${NEXT_IMAGE_UNOPTIMIZED:-true}
|
||||||
container_name: privydrop-frontend
|
container_name: privydrop-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:3001}
|
||||||
|
- BACKEND_INTERNAL_URL=${BACKEND_INTERNAL_URL:-http://backend:3001}
|
||||||
|
- NEXT_IMAGE_UNOPTIMIZED=${NEXT_IMAGE_UNOPTIMIZED:-true}
|
||||||
- PORT=3002
|
- PORT=3002
|
||||||
- HOSTNAME=0.0.0.0
|
- HOSTNAME=0.0.0.0
|
||||||
- NODE_EXTRA_CA_CERTS=/opt/privydrop/ssl/ca-cert.pem
|
- NODE_EXTRA_CA_CERTS=/opt/privydrop/ssl/ca-cert.pem
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ log_error() {
|
|||||||
|
|
||||||
# Defaults and global parameters
|
# Defaults and global parameters
|
||||||
WITH_TURN="${WITH_TURN:-false}"
|
WITH_TURN="${WITH_TURN:-false}"
|
||||||
|
WITH_NGINX="${WITH_NGINX:-false}"
|
||||||
TURN_EXTERNAL_IP_OVERRIDE=""
|
TURN_EXTERNAL_IP_OVERRIDE=""
|
||||||
TURN_MIN_PORT_DEFAULT=49152
|
TURN_MIN_PORT_DEFAULT=49152
|
||||||
TURN_MAX_PORT_DEFAULT=49252
|
TURN_MAX_PORT_DEFAULT=49252
|
||||||
@@ -95,6 +96,7 @@ Options:
|
|||||||
public: Public HTTP + TURN (no domain)
|
public: Public HTTP + TURN (no domain)
|
||||||
full: Domain + HTTPS (Let’s Encrypt) + TURN
|
full: Domain + HTTPS (Let’s Encrypt) + TURN
|
||||||
--with-turn Enable TURN in any mode. Default external-ip=LOCAL_IP
|
--with-turn Enable TURN in any mode. Default external-ip=LOCAL_IP
|
||||||
|
--with-nginx Indicate Nginx reverse proxy is enabled (frontdoor same-origin)
|
||||||
--turn-external-ip IP Explicit TURN external-ip; if not set, use PUBLIC_IP, otherwise fallback to LOCAL_IP
|
--turn-external-ip IP Explicit TURN external-ip; if not set, use PUBLIC_IP, otherwise fallback to LOCAL_IP
|
||||||
--turn-port-range R TURN UDP port range, format MIN-MAX; default 49152-49252
|
--turn-port-range R TURN UDP port range, format MIN-MAX; default 49152-49252
|
||||||
--domain DOMAIN Domain (for Nginx/certs/TURN realm, e.g., turn.DOMAIN)
|
--domain DOMAIN Domain (for Nginx/certs/TURN realm, e.g., turn.DOMAIN)
|
||||||
@@ -174,6 +176,7 @@ generate_env_file() {
|
|||||||
# Compute access endpoints for different deployment modes
|
# Compute access endpoints for different deployment modes
|
||||||
# Support both localhost and host IP for browser access; helpful for Docker direct access or local debugging
|
# Support both localhost and host IP for browser access; helpful for Docker direct access or local debugging
|
||||||
local cors_origin="http://${LOCAL_IP}:3002,http://localhost:3002"
|
local cors_origin="http://${LOCAL_IP}:3002,http://localhost:3002"
|
||||||
|
# API URL exposed to browser. When WITH_NGINX=true, prefer same-origin (empty => use relative /api)
|
||||||
local api_url="http://${LOCAL_IP}:3001"
|
local api_url="http://${LOCAL_IP}:3001"
|
||||||
local ssl_mode="none"
|
local ssl_mode="none"
|
||||||
local turn_enabled="false"
|
local turn_enabled="false"
|
||||||
@@ -188,22 +191,36 @@ generate_env_file() {
|
|||||||
lan-http)
|
lan-http)
|
||||||
# Allow both dev ports and nginx origins to avoid CORS when --with-nginx is used
|
# Allow both dev ports and nginx origins to avoid CORS when --with-nginx is used
|
||||||
cors_origin="http://${LOCAL_IP}:3002,http://localhost:3002,http://${LOCAL_IP},http://localhost"
|
cors_origin="http://${LOCAL_IP}:3002,http://localhost:3002,http://${LOCAL_IP},http://localhost"
|
||||||
api_url="http://${LOCAL_IP}:3001"
|
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||||
|
# Same-origin via Nginx (frontend uses relative /api)
|
||||||
|
api_url=""
|
||||||
|
else
|
||||||
|
api_url="http://${LOCAL_IP}:3001"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
lan-tls)
|
lan-tls)
|
||||||
if [[ "$WEB_HTTPS_ENABLED" == "true" ]]; then
|
if [[ "$WEB_HTTPS_ENABLED" == "true" ]]; then
|
||||||
HTTPS_LISTEN_PORT="8443"
|
HTTPS_LISTEN_PORT="8443"
|
||||||
# Allow common dev origins to avoid CORS when accessing via http://localhost and :3002
|
# Allow HTTP for local debug; HTTPS is exposed on 8443 by default
|
||||||
# Keep API URL on HTTPS to go through nginx TLS
|
|
||||||
cors_origin="https://${LOCAL_IP}:${HTTPS_LISTEN_PORT},https://localhost:${HTTPS_LISTEN_PORT},http://${LOCAL_IP},http://${LOCAL_IP}:3002,http://localhost,http://localhost:3002"
|
cors_origin="https://${LOCAL_IP}:${HTTPS_LISTEN_PORT},https://localhost:${HTTPS_LISTEN_PORT},http://${LOCAL_IP},http://${LOCAL_IP}:3002,http://localhost,http://localhost:3002"
|
||||||
api_url="https://${LOCAL_IP}:${HTTPS_LISTEN_PORT}"
|
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||||
|
# Same-origin via Nginx (relative /api), TLS is terminated by Nginx
|
||||||
|
api_url=""
|
||||||
|
else
|
||||||
|
api_url="https://${LOCAL_IP}:${HTTPS_LISTEN_PORT}"
|
||||||
|
fi
|
||||||
ssl_mode="self-signed"
|
ssl_mode="self-signed"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
public)
|
public)
|
||||||
local effective_public_host="${PUBLIC_IP:-$LOCAL_IP}"
|
local effective_public_host="${PUBLIC_IP:-$LOCAL_IP}"
|
||||||
cors_origin="http://${effective_public_host}:3002,http://localhost:3002,http://${effective_public_host},http://localhost"
|
cors_origin="http://${effective_public_host}:3002,http://localhost:3002,http://${effective_public_host},http://localhost"
|
||||||
api_url="http://${effective_public_host}:3001"
|
if [[ "$WITH_NGINX" == "true" ]]; then
|
||||||
|
# Same-origin via Nginx gateway
|
||||||
|
api_url=""
|
||||||
|
else
|
||||||
|
api_url="http://${effective_public_host}:3001"
|
||||||
|
fi
|
||||||
turn_enabled="true"
|
turn_enabled="true"
|
||||||
;;
|
;;
|
||||||
full)
|
full)
|
||||||
@@ -287,6 +304,11 @@ HTTP_PORT=80
|
|||||||
HTTPS_PORT=${HTTPS_LISTEN_PORT:-443}
|
HTTPS_PORT=${HTTPS_LISTEN_PORT:-443}
|
||||||
DOCKER_HTTPS_CONTAINER_PORT=${docker_https_container_port}
|
DOCKER_HTTPS_CONTAINER_PORT=${docker_https_container_port}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Internal backend URL for server-side (frontend container only)
|
||||||
|
# =============================================================================
|
||||||
|
BACKEND_INTERNAL_URL=http://backend:3001
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Redis config
|
# Redis config
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -924,6 +946,10 @@ main() {
|
|||||||
ENABLE_SNI443=false
|
ENABLE_SNI443=false
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--with-nginx)
|
||||||
|
WITH_NGINX=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
--enable-web-https)
|
--enable-web-https)
|
||||||
WEB_HTTPS_ENABLED=true
|
WEB_HTTPS_ENABLED=true
|
||||||
HTTPS_LISTEN_PORT="8443"
|
HTTPS_LISTEN_PORT="8443"
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ ARG NEXT_PUBLIC_API_URL
|
|||||||
ARG NEXT_PUBLIC_TURN_HOST
|
ARG NEXT_PUBLIC_TURN_HOST
|
||||||
ARG NEXT_PUBLIC_TURN_USERNAME
|
ARG NEXT_PUBLIC_TURN_USERNAME
|
||||||
ARG NEXT_PUBLIC_TURN_PASSWORD
|
ARG NEXT_PUBLIC_TURN_PASSWORD
|
||||||
|
ARG NEXT_IMAGE_UNOPTIMIZED
|
||||||
|
|
||||||
# Inject public env vars during frontend build (for client direct access to backend and TURN)
|
# Inject public env vars during frontend build (for client direct access to backend and TURN)
|
||||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
ENV NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
ENV NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
||||||
ENV NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
ENV NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
||||||
ENV NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
ENV NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
||||||
|
ENV NEXT_IMAGE_UNOPTIMIZED=${NEXT_IMAGE_UNOPTIMIZED}
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
@@ -81,7 +83,9 @@ ARG NEXT_PUBLIC_API_URL
|
|||||||
ARG NEXT_PUBLIC_TURN_HOST
|
ARG NEXT_PUBLIC_TURN_HOST
|
||||||
ARG NEXT_PUBLIC_TURN_USERNAME
|
ARG NEXT_PUBLIC_TURN_USERNAME
|
||||||
ARG NEXT_PUBLIC_TURN_PASSWORD
|
ARG NEXT_PUBLIC_TURN_PASSWORD
|
||||||
|
ARG NEXT_IMAGE_UNOPTIMIZED
|
||||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
ENV NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
ENV NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST}
|
||||||
ENV NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
ENV NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME}
|
||||||
ENV NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
ENV NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD}
|
||||||
|
ENV NEXT_IMAGE_UNOPTIMIZED=${NEXT_IMAGE_UNOPTIMIZED}
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ export async function GET(request: NextRequest) {
|
|||||||
environment: process.env.NODE_ENV || 'development'
|
environment: process.env.NODE_ENV || 'development'
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查后端API连接
|
// Check backend API connectivity
|
||||||
const backendHealth = await checkBackendHealth();
|
const backendHealth = await checkBackendHealth();
|
||||||
if (backendHealth.status !== 'connected') {
|
if (backendHealth.status !== 'connected') {
|
||||||
errors.push('Backend API connection failed');
|
errors.push('Backend API connection failed');
|
||||||
status = 'degraded';
|
status = 'degraded';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 系统信息
|
// System info snapshot
|
||||||
const systemInfo = {
|
const systemInfo = {
|
||||||
runtime: process.env.NEXT_RUNTIME || 'nodejs',
|
runtime: process.env.NEXT_RUNTIME || 'nodejs',
|
||||||
nextjs: {
|
nextjs: {
|
||||||
@@ -64,10 +64,11 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查后端API健康状态
|
// Check backend API health
|
||||||
async function checkBackendHealth() {
|
async function checkBackendHealth() {
|
||||||
try {
|
try {
|
||||||
const backendUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
// Prefer container-internal URL, then public URL, then localhost fallback
|
||||||
|
const backendUrl = process.env.BACKEND_INTERNAL_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
const response = await fetch(`${backendUrl}/health`, {
|
const response = await fetch(`${backendUrl}/health`, {
|
||||||
@@ -75,7 +76,7 @@ async function checkBackendHealth() {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
// 设置超时时间
|
// Timeout to avoid long-hanging connections in degraded networks
|
||||||
signal: AbortSignal.timeout(5000)
|
signal: AbortSignal.timeout(5000)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ async function checkBackendHealth() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
status: 'disconnected',
|
status: 'disconnected',
|
||||||
backendUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
|
backendUrl: process.env.BACKEND_INTERNAL_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
|
||||||
error: error instanceof Error ? error.message : 'Unknown error'
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -114,4 +115,4 @@ function formatBytes(bytes: number): string {
|
|||||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { ManagerOptions, SocketOptions } from "socket.io-client";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
API_URL: process.env.NEXT_PUBLIC_API_URL!,
|
API_URL: process.env.NEXT_PUBLIC_API_URL!,
|
||||||
USE_HTTPS: process.env.NODE_ENV !== "development",
|
USE_HTTPS: process.env.NODE_ENV !== "development",
|
||||||
@@ -54,14 +56,12 @@ export const getIceServers = () => {
|
|||||||
return iceServers;
|
return iceServers;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSocketOptions = () => {
|
export const getSocketOptions = (): Partial<ManagerOptions & SocketOptions> => {
|
||||||
return config.USE_HTTPS
|
// Allow polling fallback; do not force "secure" here — protocol will be inferred
|
||||||
? {
|
return {
|
||||||
secure: true,
|
path: "/socket.io/",
|
||||||
path: "/socket.io/",
|
transports: ["websocket", "polling"],
|
||||||
transports: ["websocket"],
|
};
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFetchOptions = (options: RequestInit = {}): RequestInit => {
|
export const getFetchOptions = (options: RequestInit = {}): RequestInit => {
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ class WebRTCService {
|
|||||||
private static instance: WebRTCService;
|
private static instance: WebRTCService;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
|
const apiUrl = (config.API_URL || "").trim();
|
||||||
|
// Use same-origin when API_URL is empty string — socket.io accepts empty string for same-origin
|
||||||
|
const signalingServer: string = apiUrl.length > 0 ? apiUrl : "";
|
||||||
const webRTCConfig = {
|
const webRTCConfig = {
|
||||||
iceServers: getIceServers(),
|
iceServers: getIceServers(),
|
||||||
socketOptions: getSocketOptions() || {},
|
socketOptions: getSocketOptions() || {},
|
||||||
signalingServer: config.API_URL,
|
signalingServer,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sender = new WebRTC_Initiator(webRTCConfig);
|
this.sender = new WebRTC_Initiator(webRTCConfig);
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const withMDX = createMDX({
|
|||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
|
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
|
||||||
images: {
|
images: {
|
||||||
|
// Disable optimization inside Docker to avoid container loopback fetch failures (502)
|
||||||
|
unoptimized: process.env.NEXT_IMAGE_UNOPTIMIZED === 'true',
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
@@ -28,4 +30,4 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withMDX(nextConfig);
|
export default withMDX(nextConfig);
|
||||||
|
|||||||
Reference in New Issue
Block a user