Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions docs/content/scripts/google-maps.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,39 @@ Optionally, you can provide permissions to the [Static Maps API](https://develop

Showing an interactive JS map requires the Maps JavaScript API, which is a paid service. If a user interacts with the map, the following costs will be incurred:
- $7 per 1000 loads for the Maps JavaScript API (default for using Google Maps)
- $2 per 1000 loads for the Static Maps API - Only used when you don't provide a `placeholder` slot.
- $5 per 1000 loads for the Geocoding API - Only used when you don't provide a `google.maps.LatLng` object instead of a query string for the `center` prop
- $2 per 1000 loads for the Static Maps API - Only used when you don't provide a `placeholder` slot. The `googleStaticMapsProxy` **caches these** automatically.
- $5 per 1000 loads for the Geocoding API - Only used when you don't provide a `google.maps.LatLng` object instead of a query string for the `center` prop. The `googleGeocodeProxy` **caches these** automatically.

However, if the user never engages with the map, only the Static Maps API usage ($2 per 1000 loads) will be charged, assuming you're using it.

Billing will be optimized in a [future update](https://github.com/nuxt/scripts/issues/83).

You should consider using the [Iframe Embed](https://developers.google.com/maps/documentation/embed/get-started) instead if you want to avoid these costs
and are okay with a less interactive map.

#### Cost Optimization Proxies

When `googleMaps` is in your registry config, server-side proxies are **automatically enabled** to cache API responses, hide your API key, and reduce billing. You can customize cache durations or disable them:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
scripts: {
registry: {
googleMaps: { apiKey: 'YOUR_KEY' }, // auto-enables proxies
},
// Optional: customize cache durations
googleStaticMapsProxy: { cacheMaxAge: 7200 },
googleGeocodeProxy: { cacheMaxAge: 172800 },
// Or disable: googleGeocodeProxy: { enabled: false },
},
})
```

| Proxy | API Saved | Cache Default | What It Does |
|-------|-----------|---------------|--------------|
| `googleStaticMapsProxy` | Static Maps ($2/1k) | 1 hour | Caches placeholder images, hides API key |
| `googleGeocodeProxy` | Places ($5/1k) | 24 hours | Caches place name β†’ coordinate lookups |

**Security:** Both proxies include IP-based rate limiting, referer validation, and CSRF token protection (geocode proxy). The server injects API keys at request time, keeping them hidden from the client.

### Demo

::code-group
Expand Down
57 changes: 50 additions & 7 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,23 @@ export interface ModuleOptions {
*/
cacheMaxAge?: number
}
/**
* Google Geocode proxy configuration.
* Proxies Places API geocoding through your server with aggressive caching
* to reduce API costs for place name to coordinate resolution.
*/
googleGeocodeProxy?: {
/**
* Enable geocode proxying through your own origin.
* @default false
*/
enabled?: boolean
/**
* Cache duration for geocode results in seconds.
* @default 86400 (24 hours)
*/
cacheMaxAge?: number
}
/**
* Whether the module is enabled.
*
Expand Down Expand Up @@ -311,9 +328,11 @@ export default defineNuxtModule<ModuleOptions>({
},
},
googleStaticMapsProxy: {
enabled: false,
cacheMaxAge: 3600,
},
googleGeocodeProxy: {
cacheMaxAge: 86400,
},
enabled: true,
debug: false,
},
Expand All @@ -335,20 +354,32 @@ export default defineNuxtModule<ModuleOptions>({
if (unheadVersion?.startsWith('1')) {
logger.error(`Nuxt Scripts requires Unhead >= 2, you are using v${unheadVersion}. Please run \`nuxi upgrade --clean\` to upgrade...`)
}
const mapsApiKey = (nuxt.options.runtimeConfig.public.scripts as any)?.googleMaps?.apiKey
// Auto-enable Google Maps proxies when googleMaps is in the registry
const hasGoogleMaps = !!config.registry?.googleMaps
const staticMapsEnabled = config.googleStaticMapsProxy?.enabled ?? hasGoogleMaps
const geocodeEnabled = config.googleGeocodeProxy?.enabled ?? hasGoogleMaps

nuxt.options.runtimeConfig['nuxt-scripts'] = {
version: version!,
// Private proxy config with API key (server-side only)
googleStaticMapsProxy: config.googleStaticMapsProxy?.enabled
? { apiKey: (nuxt.options.runtimeConfig.public.scripts as any)?.googleMaps?.apiKey }
googleStaticMapsProxy: staticMapsEnabled
? { apiKey: mapsApiKey }
: undefined,
googleGeocodeProxy: geocodeEnabled
? { apiKey: mapsApiKey }
: undefined,
Comment on lines +357 to 371
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Populate proxy API keys from the registry config before enabling the handlers.

Line 357 reads the key from nuxt.options.runtimeConfig.public.scripts, but Lines 388-395 only merge config.registry into that object later. With the documented config shape scripts.registry.googleMaps = { apiKey: '...' }, both proxies are auto-enabled here and their private runtime config ends up with apiKey: undefined, so the handlers 500 at runtime. Read the key from config.registry.googleMaps first, or compute this after the merge.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/module.ts` around lines 357 - 371, The code reads mapsApiKey from
nuxt.options.runtimeConfig.public.scripts before merging registry values,
causing mapsApiKey to be undefined when the key is provided in
config.registry.googleMaps; update the logic so mapsApiKey is sourced from
config.registry.googleMaps?.apiKey (or compute mapsApiKey after merging
config.registry into nuxt.options.runtimeConfig.public.scripts) before
constructing nuxt.options.runtimeConfig['nuxt-scripts'] so that
googleStaticMapsProxy and googleGeocodeProxy receive the real API key when
staticMapsEnabled or geocodeEnabled are true; adjust the variables mapsApiKey,
staticMapsEnabled, geocodeEnabled, and the
nuxt.options.runtimeConfig['nuxt-scripts'] assignment accordingly.

} as any
nuxt.options.runtimeConfig.public['nuxt-scripts'] = {
// expose for devtools
version: nuxt.options.dev ? version : undefined,
defaultScriptOptions: config.defaultScriptOptions as any,
// Only expose enabled and cacheMaxAge to client, not apiKey
googleStaticMapsProxy: config.googleStaticMapsProxy?.enabled
? { enabled: true, cacheMaxAge: config.googleStaticMapsProxy.cacheMaxAge }
googleStaticMapsProxy: staticMapsEnabled
? { enabled: true, cacheMaxAge: config.googleStaticMapsProxy?.cacheMaxAge }
: undefined,
googleGeocodeProxy: geocodeEnabled
? { enabled: true, cacheMaxAge: config.googleGeocodeProxy?.cacheMaxAge }
: undefined,
} as any

Expand Down Expand Up @@ -695,14 +726,26 @@ export default defineNuxtModule<ModuleOptions>({
})
})

// Add Google Static Maps proxy handler if enabled
if (config.googleStaticMapsProxy?.enabled) {
// Add Google Maps proxy handlers (auto-enabled when googleMaps is in registry)
if (staticMapsEnabled) {
addServerHandler({
route: '/_scripts/google-static-maps-proxy',
handler: await resolvePath('./runtime/server/google-static-maps-proxy'),
})
}

if (geocodeEnabled) {
addServerHandler({
route: '/_scripts/google-maps-geocode-proxy',
handler: await resolvePath('./runtime/server/google-maps-geocode-proxy'),
})
// CSRF cookie middleware for geocode proxy protection
addServerHandler({
middleware: true,
handler: await resolvePath('./runtime/server/middleware/proxy-csrf'),
})
}

// Add Gravatar proxy handler when registry.gravatar is enabled
if (config.registry?.gravatar) {
const gravatarConfig = typeof config.registry.gravatar === 'object' && !Array.isArray(config.registry.gravatar)
Expand Down
23 changes: 22 additions & 1 deletion src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useScriptGoogleMaps } from '#nuxt-scripts/registry/google-maps'
import { scriptRuntimeConfig } from '#nuxt-scripts/utils'
import { defu } from 'defu'
import { tryUseNuxtApp, useHead, useRuntimeConfig } from 'nuxt/app'
import { $fetch } from 'ofetch'
import { hash } from 'ohash'
import { withQuery } from 'ufo'
import { computed, onBeforeUnmount, onMounted, provide, ref, shallowRef, toRaw, watch } from 'vue'
Expand Down Expand Up @@ -132,6 +133,14 @@ const emits = defineEmits<{
const apiKey = props.apiKey || scriptRuntimeConfig('googleMaps')?.apiKey
const runtimeConfig = useRuntimeConfig()
const proxyConfig = (runtimeConfig.public['nuxt-scripts'] as any)?.googleStaticMapsProxy
const geocodeProxyConfig = (runtimeConfig.public['nuxt-scripts'] as any)?.googleGeocodeProxy

// Read CSRF token from cookie on client for geocode proxy requests
function getCsrfToken(): string {
if (import.meta.server)
return ''
return document.cookie.split('; ').find(c => c.startsWith('__nuxt_scripts_proxy='))?.split('=')[1] || ''
}

// Color mode support - try to auto-detect from @nuxtjs/color-mode
const nuxtApp = tryUseNuxtApp()
Expand Down Expand Up @@ -249,7 +258,19 @@ async function resolveQueryToLatLang(query: string) {
if (queryToLatLngCache.has(query)) {
return Promise.resolve(queryToLatLngCache.get(query))
}
// only if the query is a string we need to do a lookup
// Use server-side geocode proxy when enabled to save Places API costs
if (geocodeProxyConfig?.enabled) {
const data = await $fetch<{ lat: number, lng: number }>('/_scripts/google-maps-geocode-proxy', {
query: { input: query },
headers: { 'x-nuxt-scripts-token': getCsrfToken() },
}).catch(() => null)
if (data) {
const latLng = new mapsApi.value!.LatLng(data.lat, data.lng)
queryToLatLngCache.set(query, latLng)
return latLng
}
}
Comment on lines +261 to +272
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle case where mapsApi is not yet loaded.

The proxy path assumes mapsApi.value is available, but this function is exposed via defineExpose (line 330) and could be called before the Maps API loads. The fallback path (lines 268-277) properly awaits API loading, but the proxy path doesn't.

Consider either awaiting mapsApi or returning a plain object that doesn't require the Maps API:

Option 1: Return plain coordinates (avoids Maps API dependency)
   if (geocodeProxyConfig?.enabled) {
     const data = await $fetch<{ lat: number, lng: number }>('/_scripts/google-maps-geocode-proxy', {
       query: { input: query },
     }).catch(() => null)
     if (data) {
-      const latLng = new mapsApi.value!.LatLng(data.lat, data.lng)
-      queryToLatLngCache.set(query, latLng)
-      return latLng
+      // Return LatLngLiteral - compatible with most Maps API methods
+      const latLng = { lat: data.lat, lng: data.lng }
+      queryToLatLngCache.set(query, latLng as any)
+      return latLng as any
     }
   }
Option 2: Ensure Maps API is loaded first
   if (geocodeProxyConfig?.enabled) {
     const data = await $fetch<{ lat: number, lng: number }>('/_scripts/google-maps-geocode-proxy', {
       query: { input: query },
     }).catch(() => null)
     if (data) {
+      if (!mapsApi.value) {
+        await load()
+        await new Promise<void>((resolve) => {
+          const stop = watch(mapsApi, () => { stop(); resolve() })
+        })
+      }
       const latLng = new mapsApi.value!.LatLng(data.lat, data.lng)
       queryToLatLngCache.set(query, latLng)
       return latLng
     }
   }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Use server-side geocode proxy when enabled to save Places API costs
if (geocodeProxyConfig?.enabled) {
const data = await $fetch<{ lat: number, lng: number }>('/_scripts/google-maps-geocode-proxy', {
query: { input: query },
}).catch(() => null)
if (data) {
const latLng = new mapsApi.value!.LatLng(data.lat, data.lng)
queryToLatLngCache.set(query, latLng)
return latLng
}
}
// Use server-side geocode proxy when enabled to save Places API costs
if (geocodeProxyConfig?.enabled) {
const data = await $fetch<{ lat: number, lng: number }>('/_scripts/google-maps-geocode-proxy', {
query: { input: query },
}).catch(() => null)
if (data) {
// Return LatLngLiteral - compatible with most Maps API methods
const latLng = { lat: data.lat, lng: data.lng }
queryToLatLngCache.set(query, latLng as any)
return latLng as any
}
}
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue` around lines 254 -
264, The proxy branch assumes mapsApi.value exists and will throw if called
before the Maps API loads; update the proxy branch that uses geocodeProxyConfig
to either await the Maps API (e.g., await mapsApi to be initialized before
creating new mapsApi.value.LatLng) or return/store a plain {lat, lng} fallback
so callers (and queryToLatLngCache) don't require the Maps API instance;
specifically, inside the geocodeProxyConfig?.enabled block check mapsApi.value
and if missing either await the loader that populates mapsApi or cache/return
the raw coordinates and convert to mapsApi.value.LatLng later (so defineExpose'd
methods can be called before the API is ready).

// Fallback to client-side Places API
// eslint-disable-next-line no-async-promise-executor
return new Promise<google.maps.LatLng>(async (resolve, reject) => {
if (!mapsApi.value) {
Expand Down
124 changes: 124 additions & 0 deletions src/runtime/server/google-maps-geocode-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { useRuntimeConfig } from '#imports'
import { createError, defineEventHandler, getHeader, getQuery, getRequestIP, setHeader } from 'h3'
import { $fetch } from 'ofetch'
import { withQuery } from 'ufo'
import { validateProxyCsrf } from './utils/proxy-csrf'

const MAX_INPUT_LENGTH = 200
const RATE_LIMIT_WINDOW_MS = 60_000
const RATE_LIMIT_MAX = 30

const requestCounts = new Map<string, { count: number, resetAt: number }>()

function checkRateLimit(ip: string): boolean {
const now = Date.now()
const entry = requestCounts.get(ip)
if (!entry || now > entry.resetAt) {
requestCounts.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS })
return true
}
entry.count++
return entry.count <= RATE_LIMIT_MAX
}
Comment on lines +11 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Memory leak: rate limit entries are never evicted (same issue as static maps proxy).

This is the same unbounded memory growth issue present in google-static-maps-proxy.ts. Consider extracting a shared rate limiting utility with periodic cleanup for both proxies.

πŸ›‘οΈ Proposed fix with periodic cleanup
 const requestCounts = new Map<string, { count: number, resetAt: number }>()
+
+// Clean up stale entries periodically (every 5 minutes)
+setInterval(() => {
+  const now = Date.now()
+  for (const [ip, entry] of requestCounts) {
+    if (now > entry.resetAt) {
+      requestCounts.delete(ip)
+    }
+  }
+}, 5 * 60 * 1000).unref()
 
 function checkRateLimit(ip: string): boolean {

Consider extracting the rate limiting logic into a shared utility module (e.g., src/runtime/server/utils/rate-limit.ts) to eliminate duplication between both proxy handlers.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/server/google-maps-geocode-proxy.ts` around lines 10 - 21, The
in-memory Map requestCounts used by checkRateLimit (with RATE_LIMIT_WINDOW_MS
and RATE_LIMIT_MAX) will grow without bound; extract this logic into a shared
rate-limit utility (e.g., a new rate-limit module) and replace the local
requestCounts/checkRateLimit with calls to that utility which implements
eviction: store per-key {count, resetAt}, increment/reset as now does, and run a
periodic cleanup (setInterval) to remove entries whose resetAt < Date.now() to
prevent memory leaks; ensure both google-maps-geocode-proxy and
google-static-maps-proxy call the same exported functions (e.g.,
createRateLimiter or checkAndRecord) so logic is centralized and duplicated maps
are removed.


export default defineEventHandler(async (event) => {
const runtimeConfig = useRuntimeConfig()
const publicConfig = (runtimeConfig.public['nuxt-scripts'] as any)?.googleGeocodeProxy
const privateConfig = (runtimeConfig['nuxt-scripts'] as any)?.googleGeocodeProxy

if (!publicConfig?.enabled) {
throw createError({
statusCode: 404,
statusMessage: 'Google Geocode proxy is not enabled',
})
}

const apiKey = privateConfig?.apiKey
if (!apiKey) {
throw createError({
statusCode: 500,
statusMessage: 'Google Maps API key not configured for geocode proxy',
})
}

// CSRF validation (double-submit cookie pattern)
validateProxyCsrf(event)

// Rate limit by IP
const ip = getRequestIP(event, { xForwardedFor: true }) || 'unknown'
if (!checkRateLimit(ip)) {
throw createError({
statusCode: 429,
statusMessage: 'Too many geocode requests',
})
}

// Validate referer to prevent external abuse
const referer = getHeader(event, 'referer')
const host = getHeader(event, 'host')
if (referer && host) {
let refererHost: string | undefined
try {
refererHost = new URL(referer).host
}
catch {}
if (refererHost && refererHost !== host) {
throw createError({
statusCode: 403,
statusMessage: 'Invalid referer',
})
}
}
Comment on lines +56 to +71
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Require origin metadata instead of skipping the check when it's absent.

This only rejects requests when both referer and host are present and differ. A scripted client can omit Referer, mint a valid CSRF cookie/header pair, and still call the proxy directly. If this endpoint is meant to stay same-origin only, reject when neither a same-origin Origin nor Referer is present.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/server/google-maps-geocode-proxy.ts` around lines 56 - 71, The
referer/host check should require origin metadata instead of silently skipping
when absent: fetch Origin via getHeader(event, 'origin') and use it as the
primary check (fall back to Referer if Origin is not present), parse the
origin/referer host in a try/catch (like refererHost) and compare that host to
the request host; if neither Origin nor Referer are present, or if the parsed
origin/referer host differs from host, throw createError({ statusCode: 403,
statusMessage: 'Invalid origin' }) to enforce same-origin-only access (update
variable names such as origin, referer, refererHost/originHost and reuse
createError).


const query = getQuery(event)
const input = query.input as string

if (!input) {
throw createError({
statusCode: 400,
statusMessage: 'Missing "input" query parameter',
})
}

if (input.length > MAX_INPUT_LENGTH) {
throw createError({
statusCode: 400,
statusMessage: `Input too long (max ${MAX_INPUT_LENGTH} characters)`,
})
Comment on lines +73 to +87
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Validate that input is a single string, not just truthy.

getQuery() can return string[], but query.input as string lets that through unchecked. That can produce malformed upstream requests instead of a clean 400.

Suggested fix
   const query = getQuery(event)
-  const input = query.input as string
+  const input = query.input
 
-  if (!input) {
+  if (typeof input !== 'string' || !input.trim()) {
     throw createError({
       statusCode: 400,
       statusMessage: 'Missing "input" query parameter',
     })
   }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const query = getQuery(event)
const input = query.input as string
if (!input) {
throw createError({
statusCode: 400,
statusMessage: 'Missing "input" query parameter',
})
}
if (input.length > MAX_INPUT_LENGTH) {
throw createError({
statusCode: 400,
statusMessage: `Input too long (max ${MAX_INPUT_LENGTH} characters)`,
})
const query = getQuery(event)
const input = query.input
if (typeof input !== 'string' || !input.trim()) {
throw createError({
statusCode: 400,
statusMessage: 'Missing "input" query parameter',
})
}
if (input.length > MAX_INPUT_LENGTH) {
throw createError({
statusCode: 400,
statusMessage: `Input too long (max ${MAX_INPUT_LENGTH} characters)`,
})
}
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/server/google-maps-geocode-proxy.ts` around lines 73 - 87, The
code assumes query.input is a string but getQuery() may return string[]; update
the validation around query/input (referencing getQuery, query, input,
MAX_INPUT_LENGTH, createError) to explicitly reject non-string or array values:
if Array.isArray(input) or typeof input !== 'string' throw createError with a
400 and a clear message like 'Invalid "input" query parameter; must be a single
string'; keep the existing length check for valid strings afterwards.

}

const googleUrl = withQuery('https://maps.googleapis.com/maps/api/place/findplacefromtext/json', {
input,
inputtype: 'textquery',
fields: 'name,geometry',
key: apiKey,
})

const data = await $fetch<{ candidates: Array<{ geometry: { location: { lat: number, lng: number } }, name: string }>, status: string }>(googleUrl, {
headers: {
'User-Agent': 'Nuxt Scripts Google Geocode Proxy',
},
}).catch((error: any) => {
throw createError({
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || 'Failed to geocode query',
})
})

if (data.status !== 'OK' || !data.candidates?.[0]?.geometry?.location) {
throw createError({
statusCode: 404,
statusMessage: `No location found for "${input}"`,
})
}
Comment on lines +108 to +113
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

User input is reflected in error messageβ€”consider sanitizing.

The raw input query parameter is interpolated into the error message. While this aids debugging, it could leak user-provided data in logs or error responses. Consider truncating or omitting the input value.

πŸ›‘οΈ Proposed fix to avoid reflecting user input
   if (data.status !== 'OK' || !data.candidates?.[0]?.geometry?.location) {
     throw createError({
       statusCode: 404,
-      statusMessage: `No location found for "${input}"`,
+      statusMessage: 'No location found for the provided query',
     })
   }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (data.status !== 'OK' || !data.candidates?.[0]?.geometry?.location) {
throw createError({
statusCode: 404,
statusMessage: `No location found for "${input}"`,
})
}
if (data.status !== 'OK' || !data.candidates?.[0]?.geometry?.location) {
throw createError({
statusCode: 404,
statusMessage: 'No location found for the provided query',
})
}
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/server/google-maps-geocode-proxy.ts` around lines 104 - 109, The
error message currently interpolates the raw user-provided variable input into
the thrown createError call in the geocode check (the if block that tests
data.status and data.candidates), which can leak user data; replace the direct
interpolation with a sanitized or omitted value (e.g., use a truncated/validated
version of input or a generic placeholder like "<redacted>" or "unspecified
query") before passing it into createError so logs and responses no longer
reflect full user input while keeping the existing error condition and
createError usage intact.


const location = data.candidates[0].geometry.location

// Cache aggressively - place names rarely change coordinates
const cacheMaxAge = publicConfig.cacheMaxAge || 86400
setHeader(event, 'Content-Type', 'application/json')
setHeader(event, 'Cache-Control', `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`)
Comment on lines +117 to +120
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Honor cacheMaxAge: 0 instead of forcing the default.

Using || 86400 treats 0 as unset, so callers cannot disable caching even though the option is configurable.

Suggested fix
-  const cacheMaxAge = publicConfig.cacheMaxAge || 86400
+  const cacheMaxAge = publicConfig.cacheMaxAge ?? 86400
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Cache aggressively - place names rarely change coordinates
const cacheMaxAge = publicConfig.cacheMaxAge || 86400
setHeader(event, 'Content-Type', 'application/json')
setHeader(event, 'Cache-Control', `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`)
// Cache aggressively - place names rarely change coordinates
const cacheMaxAge = publicConfig.cacheMaxAge ?? 86400
setHeader(event, 'Content-Type', 'application/json')
setHeader(event, 'Cache-Control', `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`)
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/server/google-maps-geocode-proxy.ts` around lines 117 - 120, The
code uses `publicConfig.cacheMaxAge || 86400` which treats 0 as falsy and
prevents callers from disabling caching; update the `cacheMaxAge` assignment in
`google-maps-geocode-proxy.ts` to honor 0 by using a nullish check (e.g., `??
86400`) or an explicit undefined check on `publicConfig.cacheMaxAge`, then
continue to call `setHeader(event, 'Cache-Control', \`public,
max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}\`)` so `setHeader` receives the
intended 0 when configured.

setHeader(event, 'Vary', 'Accept-Encoding')

return { lat: location.lat, lng: location.lng, name: data.candidates[0].name }
})
Loading
Loading