{"schemaVersion":1,"id":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns","title":"Scoped Remote Image Patterns over Generic CDN Proxy","description":"The portfolio displays images from multiple origins: profile photos and social cards from statics.lucioduran.com, dynamic content images from api.lucioduran.com","language":"en","datePublished":null,"dateModified":null,"contentFormat":"text/markdown","content":"ADR-016accepted20 de mayo de 2024\n\n# Scoped Remote Image Patterns over Generic CDN Proxy\n\n## Contexto\n\nThe portfolio displays images from multiple origins: profile photos and social cards from statics.lucioduran.com, dynamic content images from api.lucioduran.com, and the main domain lucioduran.com for statically hosted assets. Next.js Image component provides automatic optimization (WebP/AVIF conversion, responsive sizing, lazy loading) but requires explicit configuration of allowed remote image sources via remotePatterns in next.config.js. The configuration choice is between a permissive approach (allowing any hostname via a wildcard pattern) and a restrictive approach (explicitly listing each allowed origin). A wildcard pattern simplifies configuration but creates an open proxy, any external image URL processed through Next.js Image becomes an attack vector for SSRF (Server-Side Request Forgery), where an attacker could force the server to fetch internal network resources. For a publicly deployed site on Vercel, this could expose Vercel's internal infrastructure to probing.\n\n## Decisión\n\nConfigure next.config.js remotePatterns with exactly three explicitly listed hostnames, all under the lucioduran.com domain: statics.lucioduran.com (static assets CDN), api.lucioduran.com (dynamic content images), and lucioduran.com (root domain assets). Each entry specifies protocol: 'https', no HTTP sources allowed. No wildcard patterns, no catch-all rules, no third-party hostnames. Images from any other origin must be downloaded and self-hosted before they can be optimized through Next.js Image. This creates a closed system where every image URL processed by the optimization pipeline is from a controlled, first-party origin.\n\n## Consecuencias\n\nPositive: The image optimization endpoint cannot be used as an SSRF proxy, only three known, controlled hostnames are processable. HTTPS-only enforcement prevents accidental mixed-content serving. The explicit listing serves as documentation of the site's image infrastructure, a new developer can immediately see which origins serve images. All three origins share a common root domain (lucioduran.com), making SSL certificate management consistent. The constraint forces discipline: every image must be hosted on owned infrastructure, preventing dependency on third-party image services that could change URLs or go offline. Negative: Adding a new image source (e.g., a profile photo from a third-party service, screenshots from external tools) requires a next.config.js change and redeploy. If a content entry references an image on an unlisted domain, the Next.js Image component falls back to an unoptimized img tag or errors, depending on configuration. The three-origin limitation means all images must be uploaded to owned infrastructure first, adding a manual step to the content authoring workflow.\n\n## Incertidumbre Calibrada\n\n## Predicciones al Momento de la Decisión\n\nExpected the three-origin restriction to cover all current and near-future image sources. Predicted the manual upload step (images to owned infrastructure) would add negligible friction because image changes are infrequent. Assumed no third-party image sources would be needed.\n\n## Resultados Medidos\n\nThe three-origin restriction has covered 100% of image needs for 20+ months. No third-party image source has been required. The manual upload step is negligible as predicted, images change approximately once per quarter. The ~60% size reduction from WebP optimization is consistent across all image types (profile photos, social cards, content images). The unexpected benefit: having all images on owned infrastructure means zero risk of broken images from third-party URL changes or service discontinuations, a problem that has affected previous projects with images hotlinked from external services.\n\n## Incógnitas al Momento de la Decisión\n\nDid not know whether future content types would require images from external origins (e.g., GitHub avatar URLs, certification badge images from third-party issuers). So far they haven't, all images are self-hosted. Also unknown: whether Vercel's image optimization pricing would change for free tier users. Currently unlimited, but usage-based pricing could make aggressive image optimization costly at scale.\n\n## Clasificación de Reversibilidad\n\nPuerta de Ida y Vuelta\n\nAdding new origins to remotePatterns is a single line addition to next.config.js. Switching to a wildcard pattern (hostname: '\\*\\*') is a one-line change. Removing Image optimization entirely and using standard img tags requires replacing Next.js Image imports across components. Estimated effort for any pattern change: 5 minutes plus redeploy.\n\n## Contra-Argumento Más Fuerte\n\nThe SSRF risk for a static portfolio site on Vercel is theoretical, there are no internal network resources to probe, and Vercel's infrastructure is not vulnerable to SSRF via image optimization endpoints. A wildcard pattern would simplify the workflow for adding new image sources and eliminate the manual upload step. For a single-developer project with full control over content, the security posture is arguably over-restrictive. The counter-counter: the restrictive pattern is not just about SSRF, it's about infrastructure ownership. Depending on external image hosts creates a silent availability risk that is harder to diagnose than a build failure.\n\n## Contexto Técnico\n\nStack\n\n- Next.js Image\n- next.config.js remotePatterns\n- WebP/AVIF\n- Vercel Image Optimization\n\nAllowed Origins\n\n3\n\nProtocol Restriction\n\nHTTPS only\n\nWildcard Patterns\n\n0\n\nAvg Image Size Reduction\n\n~60% (WebP vs PNG)\n\nRestricciones\n\n- No wildcard hostname patterns\n- HTTPS only\n- All origins must be first-party controlled\n\n## Decisiones Relacionadas\n\n- [ADR-015accepted\n\n  Critical Rendering Path Optimization Strategy](<https://lucioduran.com/es/decisions/critical-rendering-path-optimization>)\n- [ADR-017accepted\n\n  Defense-in-Depth HTTP Security Headers](<https://lucioduran.com/es/decisions/defense-in-depth-security-headers>)\n\n[Volver a Decisiones](<https://lucioduran.com/es/decisions>)","headings":[{"level":1,"text":"Scoped Remote Image Patterns over Generic CDN Proxy","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#title"},{"level":2,"text":"Contexto","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-context"},{"level":2,"text":"Decisión","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-decision"},{"level":2,"text":"Consecuencias","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-consequences"},{"level":2,"text":"Incertidumbre Calibrada","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-uncertainty"},{"level":2,"text":"Predicciones al Momento de la Decisión","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-predictions"},{"level":2,"text":"Resultados Medidos","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-outcomes"},{"level":2,"text":"Incógnitas al Momento de la Decisión","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-unknowns"},{"level":2,"text":"Clasificación de Reversibilidad","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-reversibility"},{"level":2,"text":"Contra-Argumento Más Fuerte","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-counterArgument"},{"level":2,"text":"Contexto Técnico","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-techContext"},{"level":2,"text":"Decisiones Relacionadas","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-relatedDecisions"}],"sections":[{"heading":"Scoped Remote Image Patterns over Generic CDN Proxy","level":1,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns","content":"ADR-016accepted20 de mayo de 2024"},{"heading":"Scoped Remote Image Patterns over Generic CDN Proxy","level":1,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#title","content":""},{"heading":"Contexto","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-context","content":"The portfolio displays images from multiple origins: profile photos and social cards from statics.lucioduran.com, dynamic content images from api.lucioduran.com, and the main domain lucioduran.com for statically hosted assets. Next.js Image component provides automatic optimization (WebP/AVIF conversion, responsive sizing, lazy loading) but requires explicit configuration of allowed remote image sources via remotePatterns in next.config.js. The configuration choice is between a permissive approach (allowing any hostname via a wildcard pattern) and a restrictive approach (explicitly listing each allowed origin). A wildcard pattern simplifies configuration but creates an open proxy, any external image URL processed through Next.js Image becomes an attack vector for SSRF (Server-Side Request Forgery), where an attacker could force the server to fetch internal network resources. For a publicly deployed site on Vercel, this could expose Vercel's internal infrastructure to probing."},{"heading":"Decisión","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-decision","content":"Configure next.config.js remotePatterns with exactly three explicitly listed hostnames, all under the lucioduran.com domain: statics.lucioduran.com (static assets CDN), api.lucioduran.com (dynamic content images), and lucioduran.com (root domain assets). Each entry specifies protocol: 'https', no HTTP sources allowed. No wildcard patterns, no catch-all rules, no third-party hostnames. Images from any other origin must be downloaded and self-hosted before they can be optimized through Next.js Image. This creates a closed system where every image URL processed by the optimization pipeline is from a controlled, first-party origin."},{"heading":"Consecuencias","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-consequences","content":"Positive: The image optimization endpoint cannot be used as an SSRF proxy, only three known, controlled hostnames are processable. HTTPS-only enforcement prevents accidental mixed-content serving. The explicit listing serves as documentation of the site's image infrastructure, a new developer can immediately see which origins serve images. All three origins share a common root domain (lucioduran.com), making SSL certificate management consistent. The constraint forces discipline: every image must be hosted on owned infrastructure, preventing dependency on third-party image services that could change URLs or go offline. Negative: Adding a new image source (e.g., a profile photo from a third-party service, screenshots from external tools) requires a next.config.js change and redeploy. If a content entry references an image on an unlisted domain, the Next.js Image component falls back to an unoptimized img tag or errors, depending on configuration. The three-origin limitation means all images must be uploaded to owned infrastructure first, adding a manual step to the content authoring workflow."},{"heading":"Incertidumbre Calibrada","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-uncertainty","content":""},{"heading":"Predicciones al Momento de la Decisión","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-predictions","content":"Expected the three-origin restriction to cover all current and near-future image sources. Predicted the manual upload step (images to owned infrastructure) would add negligible friction because image changes are infrequent. Assumed no third-party image sources would be needed."},{"heading":"Resultados Medidos","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-outcomes","content":"The three-origin restriction has covered 100% of image needs for 20+ months. No third-party image source has been required. The manual upload step is negligible as predicted, images change approximately once per quarter. The ~60% size reduction from WebP optimization is consistent across all image types (profile photos, social cards, content images). The unexpected benefit: having all images on owned infrastructure means zero risk of broken images from third-party URL changes or service discontinuations, a problem that has affected previous projects with images hotlinked from external services."},{"heading":"Incógnitas al Momento de la Decisión","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-unknowns","content":"Did not know whether future content types would require images from external origins (e.g., GitHub avatar URLs, certification badge images from third-party issuers). So far they haven't, all images are self-hosted. Also unknown: whether Vercel's image optimization pricing would change for free tier users. Currently unlimited, but usage-based pricing could make aggressive image optimization costly at scale."},{"heading":"Clasificación de Reversibilidad","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-reversibility","content":"Puerta de Ida y Vuelta\n\nAdding new origins to remotePatterns is a single line addition to next.config.js. Switching to a wildcard pattern (hostname: '\\*\\*') is a one-line change. Removing Image optimization entirely and using standard img tags requires replacing Next.js Image imports across components. Estimated effort for any pattern change: 5 minutes plus redeploy."},{"heading":"Contra-Argumento Más Fuerte","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-counterArgument","content":"The SSRF risk for a static portfolio site on Vercel is theoretical, there are no internal network resources to probe, and Vercel's infrastructure is not vulnerable to SSRF via image optimization endpoints. A wildcard pattern would simplify the workflow for adding new image sources and eliminate the manual upload step. For a single-developer project with full control over content, the security posture is arguably over-restrictive. The counter-counter: the restrictive pattern is not just about SSRF, it's about infrastructure ownership. Depending on external image hosts creates a silent availability risk that is harder to diagnose than a build failure."},{"heading":"Contexto Técnico","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-techContext","content":"Stack\n\n- Next.js Image\n- next.config.js remotePatterns\n- WebP/AVIF\n- Vercel Image Optimization\n\nAllowed Origins\n\n3\n\nProtocol Restriction\n\nHTTPS only\n\nWildcard Patterns\n\n0\n\nAvg Image Size Reduction\n\n~60% (WebP vs PNG)\n\nRestricciones\n\n- No wildcard hostname patterns\n- HTTPS only\n- All origins must be first-party controlled"},{"heading":"Decisiones Relacionadas","level":2,"url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns#decisionDetail-relatedDecisions","content":"- [ADR-015accepted\n\n  Critical Rendering Path Optimization Strategy](<https://lucioduran.com/es/decisions/critical-rendering-path-optimization>)\n- [ADR-017accepted\n\n  Defense-in-Depth HTTP Security Headers](<https://lucioduran.com/es/decisions/defense-in-depth-security-headers>)\n\n[Volver a Decisiones](<https://lucioduran.com/es/decisions>)"}],"links":[{"text":"ADR-015acceptedCritical Rendering Path Optimization Strategy","url":"https://lucioduran.com/es/decisions/critical-rendering-path-optimization"},{"text":"ADR-017acceptedDefense-in-Depth HTTP Security Headers","url":"https://lucioduran.com/es/decisions/defense-in-depth-security-headers"},{"text":"Volver a Decisiones","url":"https://lucioduran.com/es/decisions"}],"structuredData":[{"@context":"https://schema.org/","@graph":[{"@type":"TechArticle","url":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns","name":"ADR-016: Scoped Remote Image Patterns over Generic CDN Proxy","headline":"Scoped Remote Image Patterns over Generic CDN Proxy","datePublished":"2024-05-20","author":{"@id":"https://lucioduran.com/#person"},"isPartOf":{"@id":"https://lucioduran.com/#website"},"inLanguage":"en","about":"Software Architecture Decision Record","proficiencyLevel":"Expert","dependencies":"Next.js Image, next.config.js remotePatterns, WebP/AVIF, Vercel Image Optimization"},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Decisiones de Arquitectura","item":"https://lucioduran.com/es/decisions"},{"@type":"ListItem","position":3,"name":"Scoped Remote Image Patterns over Generic CDN Proxy"}]}]}],"contentHash":"9e915dfd35ef0a1093780d8e0790008e34a493d6d0437445dbbb7841cc105fd8","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/decisions/scoped-remote-image-patterns","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":37504,"contentBytes":6120}}
