From 48e141e85f5a9d56d02a980e0f87945a7cbd1e5f Mon Sep 17 00:00:00 2001 From: Samalando <156577273+Samalando@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:28:47 +0100 Subject: [PATCH 1/7] fix: stretched hosting page --- apps/frontend/src/pages/hosting/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/src/pages/hosting/index.vue b/apps/frontend/src/pages/hosting/index.vue index 827298042b..a064c82756 100644 --- a/apps/frontend/src/pages/hosting/index.vue +++ b/apps/frontend/src/pages/hosting/index.vue @@ -78,7 +78,7 @@ src="https://cdn.modrinth.com/servers/panel-right-dark.webp" alt="" aria-hidden="true" - class="pointer-events-none h-full w-fit select-none" + class="pointer-events-none h-full w-auto select-none" /> From 412c1ee625dad8c3a0603abbfc018f4685e8509a Mon Sep 17 00:00:00 2001 From: "Michael H." Date: Thu, 20 Aug 2026 14:14:45 +0200 Subject: [PATCH 2/7] feat: frontend docker image --- .github/workflows/frontend-docker.yml | 105 ++++++++++++++++++++++++++ apps/frontend/Dockerfile | 30 ++++++++ apps/frontend/Dockerfile.dockerignore | 2 + apps/frontend/nuxt.config.ts | 1 + 4 files changed, 138 insertions(+) create mode 100644 .github/workflows/frontend-docker.yml create mode 100644 apps/frontend/Dockerfile create mode 100644 apps/frontend/Dockerfile.dockerignore diff --git a/.github/workflows/frontend-docker.yml b/.github/workflows/frontend-docker.yml new file mode 100644 index 0000000000..7f68ddf5de --- /dev/null +++ b/.github/workflows/frontend-docker.yml @@ -0,0 +1,105 @@ +name: Frontend Docker + +on: + push: + branches: + - michael/frontend-docker + - main + - prod + paths: + - 'apps/frontend/**/*' + - 'packages/ui/**/*' + - 'packages/utils/**/*' + - 'packages/assets/**/*' + - 'packages/moderation/**/*' + - 'packages/api-client/**/*' + - 'packages/blog/**/*' + - '**/pnpm-*.yaml' + - .github/workflows/frontend-docker.yml + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/prod' }} + +jobs: + build: + name: Build frontend + runs-on: namespace-profile-modrinth-frontend + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Configure environment + id: meta + env: + REF: ${{ github.ref }} + run: | + if [ "$REF" = "refs/heads/main" ]; then + echo "env=staging" >> $GITHUB_OUTPUT + else + echo "env=production" >> $GITHUB_OUTPUT + fi + + - name: Setup Node + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: .nvmrc + + - name: Enable Corepack + run: corepack enable + + - name: Set up caches + uses: namespacelabs/nscloud-cache-action@c5f8dab7560444c4bf8dbc64f1b203431873c547 # v1.6.1 + with: + cache: | + pnpm + + - name: Inject build variables + working-directory: ./apps/frontend + run: | + if [ "${{ steps.meta.outputs.env }}" == "staging" ]; then + echo "Injecting staging variables from wrangler.jsonc..." + jq -r '.env.staging.vars | to_entries[] | "\(.key)=\(.value)"' wrangler.jsonc >> $GITHUB_ENV + else + echo "Injecting production variables from wrangler.jsonc..." + jq -r '.vars | to_entries[] | "\(.key)=\(.value)"' wrangler.jsonc >> $GITHUB_ENV + fi + + - name: Install dependencies + working-directory: ./apps/frontend + run: pnpm install + + # nuxi is called directly because `pnpm build` hardcodes a heap too small for the + # Node preset, which bundles every dependency instead of externalizing them. + - name: Build frontend + working-directory: ./apps/frontend + run: pnpm exec nuxi build + env: + NITRO_PRESET: node-server + NODE_OPTIONS: '--max-old-space-size=12288' + BUILD_ENV: ${{ steps.meta.outputs.env }} + CF_PAGES_BRANCH: ${{ github.ref_name }} + CF_PAGES_COMMIT_SHA: ${{ github.sha }} + + - name: Stage Docker context + run: | + mkdir -p apps/frontend/docker-stage + cp -r apps/frontend/.output apps/frontend/docker-stage/.output + + - name: Upload Docker context + uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3 + with: + name: frontend-docker-context + retention-days: 1 + path: apps/frontend/docker-stage + + docker-build: + needs: [build] + uses: SparkUniverse/workflows/.github/workflows/docker-build.yaml@main + with: + image-name: frontend + dockerfile-path: apps/frontend/Dockerfile + artifacts-name: frontend-docker-context diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile new file mode 100644 index 0000000000..484d991b7a --- /dev/null +++ b/apps/frontend/Dockerfile @@ -0,0 +1,30 @@ +# syntax=docker/dockerfile:1 + +FROM node:24.15.0-slim + +LABEL org.opencontainers.image.source=https://github.com/modrinth/code +LABEL org.opencontainers.image.title=frontend +LABEL org.opencontainers.image.description="Modrinth website" +LABEL org.opencontainers.image.licenses=AGPL-3.0-only + +RUN apt-get update \ + && apt-get install -y --no-install-recommends dumb-init \ + && rm -rf /var/lib/apt/lists/* + +ENV NODE_ENV=production +ENV HOST=0.0.0.0 +ENV PORT=3000 + +WORKDIR /app + +# Nitro bundles every runtime dependency into .output, so no node_modules is needed. +COPY --chown=node:node .output ./.output + +USER node +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD node -e "fetch('http://127.0.0.1:'+process.env.PORT+'/robots.txt').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))" + +ENTRYPOINT ["dumb-init", "--"] +CMD ["node", "/app/.output/server/index.mjs"] diff --git a/apps/frontend/Dockerfile.dockerignore b/apps/frontend/Dockerfile.dockerignore new file mode 100644 index 0000000000..4d88ab014b --- /dev/null +++ b/apps/frontend/Dockerfile.dockerignore @@ -0,0 +1,2 @@ +* +!.output diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts index 7322ac32a9..3a73a435a5 100644 --- a/apps/frontend/nuxt.config.ts +++ b/apps/frontend/nuxt.config.ts @@ -290,6 +290,7 @@ export default defineNuxtConfig({ external: ['cloudflare:workers'], }, preset: 'cloudflare_module', + noExternals: true, cloudflare: { nodeCompat: true, }, From 0f171da1ee1f2326508e0e44b047de1693d664b5 Mon Sep 17 00:00:00 2001 From: "Michael H." Date: Thu, 20 Aug 2026 15:20:52 +0200 Subject: [PATCH 3/7] build: tweak gc, disable prerendering --- .github/workflows/frontend-docker.yml | 6 +++--- apps/frontend/nuxt.config.ts | 19 ------------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/.github/workflows/frontend-docker.yml b/.github/workflows/frontend-docker.yml index 7f68ddf5de..c8ddc97a30 100644 --- a/.github/workflows/frontend-docker.yml +++ b/.github/workflows/frontend-docker.yml @@ -72,14 +72,14 @@ jobs: working-directory: ./apps/frontend run: pnpm install - # nuxi is called directly because `pnpm build` hardcodes a heap too small for the - # Node preset, which bundles every dependency instead of externalizing them. + # nuxi is called directly because `pnpm build` hardcodes NODE_OPTIONS, which would + # drop the semi-space bump that cuts roughly 5% off the build. - name: Build frontend working-directory: ./apps/frontend run: pnpm exec nuxi build env: NITRO_PRESET: node-server - NODE_OPTIONS: '--max-old-space-size=12288' + NODE_OPTIONS: '--max-old-space-size=8192 --max-semi-space-size=64' BUILD_ENV: ${{ steps.meta.outputs.env }} CF_PAGES_BRANCH: ${{ github.ref_name }} CF_PAGES_COMMIT_SHA: ${{ github.sha }} diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts index 3a73a435a5..2bc14539f4 100644 --- a/apps/frontend/nuxt.config.ts +++ b/apps/frontend/nuxt.config.ts @@ -122,23 +122,6 @@ export default defineNuxtConfig({ }, }, hooks: { - async 'nitro:config'(nitroConfig) { - const emailTemplates = Object.keys( - await import('./src/templates/emails/index.ts').then((m) => m.default), - ) - const docTemplates = Object.keys( - await import('./src/templates/docs/index.ts').then((m) => m.default), - ) - - nitroConfig.prerender = nitroConfig.prerender || {} - nitroConfig.prerender.routes = nitroConfig.prerender.routes || [] - for (const template of emailTemplates) { - nitroConfig.prerender.routes.push(`/_internal/templates/email/${template}`) - } - for (const template of docTemplates) { - nitroConfig.prerender.routes.push(`/_internal/templates/doc/${template}`) - } - }, async 'build:before'() { // 30 minutes const TTL = 30 * 60 * 1000 @@ -326,14 +309,12 @@ export default defineNuxtConfig({ redirect: '/_internal/templates/email/**', }, '/_internal/templates/email/**': { - prerender: true, headers: { 'Content-Type': 'text/html', 'Cache-Control': 'public, max-age=3600', }, }, '/_internal/templates/doc/**': { - prerender: true, headers: { 'Content-Type': 'text/html', 'Cache-Control': 'public, max-age=3600', From d25f94a4e58628fc5738bf8d8db2e7a4b409dd6a Mon Sep 17 00:00:00 2001 From: "Michael H." Date: Thu, 20 Aug 2026 15:52:38 +0200 Subject: [PATCH 4/7] feat: pass along UA --- apps/frontend/src/composables/fetch.js | 8 ++++++++ apps/frontend/src/composables/visitor-user-agent.ts | 9 +++++++++ apps/frontend/src/helpers/api.ts | 6 ++++++ apps/frontend/src/helpers/user-agent.ts | 5 +++++ apps/frontend/src/providers/setup/modrinth-client.ts | 1 + apps/frontend/src/server/utils/api-client.ts | 10 +++++++++- 6 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 apps/frontend/src/composables/visitor-user-agent.ts create mode 100644 apps/frontend/src/helpers/user-agent.ts diff --git a/apps/frontend/src/composables/fetch.js b/apps/frontend/src/composables/fetch.js index 6533fb1ce4..e3133895b6 100644 --- a/apps/frontend/src/composables/fetch.js +++ b/apps/frontend/src/composables/fetch.js @@ -3,7 +3,9 @@ * This composable is kept for legacy code that hasn't been migrated yet. */ +import { useVisitorUserAgent } from '~/composables/visitor-user-agent.ts' import { withLabrinthCanaryHeader } from '~/helpers/canary.ts' +import { getFrontendUserAgent, VISITOR_USER_AGENT_HEADER } from '~/helpers/user-agent.ts' let cachedRateLimitKey = undefined let rateLimitKeyPromise = undefined @@ -40,6 +42,12 @@ export const useBaseFetch = async (url, options = {}, skipAuth = false) => { if (import.meta.server) { options.headers['x-ratelimit-key'] = await getRateLimitKey(config) + options.headers['User-Agent'] = getFrontendUserAgent(config.public.hash) + + const visitorUserAgent = useVisitorUserAgent() + if (visitorUserAgent) { + options.headers[VISITOR_USER_AGENT_HEADER] = visitorUserAgent + } } if (!skipAuth) { diff --git a/apps/frontend/src/composables/visitor-user-agent.ts b/apps/frontend/src/composables/visitor-user-agent.ts new file mode 100644 index 0000000000..24fff46660 --- /dev/null +++ b/apps/frontend/src/composables/visitor-user-agent.ts @@ -0,0 +1,9 @@ +import { tryUseNuxtApp } from '#imports' + +// Nuxt's `useRequestHeaders` throws outside of a setup context, and `useBaseFetch` is +// called from plenty of places that no longer have one. +export function useVisitorUserAgent(): string | undefined { + if (!import.meta.server) return undefined + + return tryUseNuxtApp()?.ssrContext?.event?.node?.req?.headers['user-agent'] +} diff --git a/apps/frontend/src/helpers/api.ts b/apps/frontend/src/helpers/api.ts index fba7385268..b001a77665 100644 --- a/apps/frontend/src/helpers/api.ts +++ b/apps/frontend/src/helpers/api.ts @@ -14,7 +14,9 @@ import { import type { Ref } from 'vue' import { useFeatureFlags } from '~/composables/featureFlags.ts' +import { useVisitorUserAgent } from '~/composables/visitor-user-agent.ts' import { withStagingArchonBaseUrl } from '~/helpers/archon.ts' +import { getFrontendUserAgent, VISITOR_USER_AGENT_HEADER } from '~/helpers/user-agent.ts' async function getRateLimitKeyFromSecretsStore(): Promise { try { @@ -33,10 +35,12 @@ export function createModrinthClient( apiBaseUrl: string archonBaseUrl: string sharedInstancesBaseUrl: string + commitHash: string rateLimitKey?: string }, ): NuxtModrinthClient { const flags = useFeatureFlags() + const visitorUserAgent = useVisitorUserAgent() const optionalFeatures = [ import.meta.dev ? (new VerboseLoggingFeature() as AbstractFeature) : undefined, ].filter(Boolean) as AbstractFeature[] @@ -46,6 +50,8 @@ export function createModrinthClient( archonBaseUrl: () => withStagingArchonBaseUrl(config.archonBaseUrl, flags.value.archonApiStaging), sharedInstancesBaseUrl: config.sharedInstancesBaseUrl, + userAgent: () => (import.meta.server ? getFrontendUserAgent(config.commitHash) : undefined), + headers: visitorUserAgent ? { [VISITOR_USER_AGENT_HEADER]: visitorUserAgent } : undefined, archonSentryCapture: () => flags.value.archonSentryCapture, rateLimitKey: config.rateLimitKey || getRateLimitKeyFromSecretsStore, features: [ diff --git a/apps/frontend/src/helpers/user-agent.ts b/apps/frontend/src/helpers/user-agent.ts new file mode 100644 index 0000000000..16216e8cf1 --- /dev/null +++ b/apps/frontend/src/helpers/user-agent.ts @@ -0,0 +1,5 @@ +export const VISITOR_USER_AGENT_HEADER = 'X-Forwarded-User-Agent' + +export function getFrontendUserAgent(commitHash: string): string { + return `modrinth/frontend/${commitHash || 'unknown'} (support@modrinth.com)` +} diff --git a/apps/frontend/src/providers/setup/modrinth-client.ts b/apps/frontend/src/providers/setup/modrinth-client.ts index a6f2c89118..1d2749a077 100644 --- a/apps/frontend/src/providers/setup/modrinth-client.ts +++ b/apps/frontend/src/providers/setup/modrinth-client.ts @@ -8,6 +8,7 @@ export function setupModrinthClientProvider(auth: Awaited { try { @@ -28,6 +30,10 @@ export function useServerModrinthClient(options?: ServerModrinthClientOptions): const sharedInstancesBaseUrl = config.sharedInstancesBaseUrl || config.public.sharedInstancesBaseUrl + const visitorUserAgent = options?.event + ? getRequestHeader(options.event, 'user-agent') + : undefined + const features = [] if (options?.authToken) { @@ -42,6 +48,8 @@ export function useServerModrinthClient(options?: ServerModrinthClientOptions): const clientConfig: NuxtClientConfig = { labrinthBaseUrl: apiBaseUrl, sharedInstancesBaseUrl, + userAgent: getFrontendUserAgent(config.public.hash), + headers: visitorUserAgent ? { [VISITOR_USER_AGENT_HEADER]: visitorUserAgent } : undefined, rateLimitKey: config.rateLimitKey || getRateLimitKeyFromSecretsStore, features, } From 23a7c4f6c990c8359cfac0fde66ec63fff330ddd Mon Sep 17 00:00:00 2001 From: "Michael H." Date: Thu, 20 Aug 2026 16:31:58 +0200 Subject: [PATCH 5/7] fix: fallback to process.env --- apps/frontend/src/composables/fetch.js | 11 ++----- apps/frontend/src/helpers/api.ts | 14 ++------ apps/frontend/src/helpers/env.ts | 22 +++++++++++++ .../src/server/middleware/site-url.ts | 32 +++++++++---------- .../routes/api/intercom/messenger-jwt.get.ts | 14 ++------ apps/frontend/src/server/utils/api-client.ts | 13 ++------ 6 files changed, 46 insertions(+), 60 deletions(-) create mode 100644 apps/frontend/src/helpers/env.ts diff --git a/apps/frontend/src/composables/fetch.js b/apps/frontend/src/composables/fetch.js index e3133895b6..dfa6650392 100644 --- a/apps/frontend/src/composables/fetch.js +++ b/apps/frontend/src/composables/fetch.js @@ -5,6 +5,7 @@ import { useVisitorUserAgent } from '~/composables/visitor-user-agent.ts' import { withLabrinthCanaryHeader } from '~/helpers/canary.ts' +import { readEnv } from '~/helpers/env.ts' import { getFrontendUserAgent, VISITOR_USER_AGENT_HEADER } from '~/helpers/user-agent.ts' let cachedRateLimitKey = undefined @@ -15,15 +16,7 @@ async function getRateLimitKey(config) { if (cachedRateLimitKey !== undefined) return cachedRateLimitKey if (!rateLimitKeyPromise) { - rateLimitKeyPromise = (async () => { - try { - const mod = 'cloudflare:workers' - const { env } = await import(/* @vite-ignore */ mod) - return await env.RATE_LIMIT_IGNORE_KEY?.get() - } catch { - return undefined - } - })() + rateLimitKeyPromise = readEnv('RATE_LIMIT_IGNORE_KEY') } cachedRateLimitKey = await rateLimitKeyPromise diff --git a/apps/frontend/src/helpers/api.ts b/apps/frontend/src/helpers/api.ts index b001a77665..4a2af135c0 100644 --- a/apps/frontend/src/helpers/api.ts +++ b/apps/frontend/src/helpers/api.ts @@ -16,19 +16,9 @@ import type { Ref } from 'vue' import { useFeatureFlags } from '~/composables/featureFlags.ts' import { useVisitorUserAgent } from '~/composables/visitor-user-agent.ts' import { withStagingArchonBaseUrl } from '~/helpers/archon.ts' +import { readEnv } from '~/helpers/env.ts' import { getFrontendUserAgent, VISITOR_USER_AGENT_HEADER } from '~/helpers/user-agent.ts' -async function getRateLimitKeyFromSecretsStore(): Promise { - try { - const mod = 'cloudflare:workers' - const { env } = await import(/* @vite-ignore */ mod) - return await env.RATE_LIMIT_IGNORE_KEY?.get() - } catch { - // Not running in Cloudflare Workers environment - return undefined - } -} - export function createModrinthClient( auth: Ref<{ token: string | undefined }>, config: { @@ -53,7 +43,7 @@ export function createModrinthClient( userAgent: () => (import.meta.server ? getFrontendUserAgent(config.commitHash) : undefined), headers: visitorUserAgent ? { [VISITOR_USER_AGENT_HEADER]: visitorUserAgent } : undefined, archonSentryCapture: () => flags.value.archonSentryCapture, - rateLimitKey: config.rateLimitKey || getRateLimitKeyFromSecretsStore, + rateLimitKey: config.rateLimitKey || (() => readEnv('RATE_LIMIT_IGNORE_KEY')), features: [ // for modrinth hosting // is skipped for normal reqs diff --git a/apps/frontend/src/helpers/env.ts b/apps/frontend/src/helpers/env.ts new file mode 100644 index 0000000000..f7bc455177 --- /dev/null +++ b/apps/frontend/src/helpers/env.ts @@ -0,0 +1,22 @@ +type SecretsStoreBinding = { get: () => Promise } + +// Cloudflare exposes plain vars and Secrets Store bindings on the Workers env. Every +// other runtime (the Docker image, local dev) only has process.env. +async function getWorkersEnv(): Promise | undefined> { + try { + const mod = 'cloudflare:workers' + const { env } = await import(/* @vite-ignore */ mod) + return env as Record + } catch { + return undefined + } +} + +export async function readEnv(name: string): Promise { + const binding = (await getWorkersEnv())?.[name] + + if (typeof binding === 'string') return binding + if (binding) return await (binding as SecretsStoreBinding).get() + + return globalThis.process?.env?.[name] +} diff --git a/apps/frontend/src/server/middleware/site-url.ts b/apps/frontend/src/server/middleware/site-url.ts index 967bef2234..d21a68b12a 100644 --- a/apps/frontend/src/server/middleware/site-url.ts +++ b/apps/frontend/src/server/middleware/site-url.ts @@ -1,20 +1,20 @@ +import { readEnv } from '~/helpers/env' + export default defineEventHandler(async (event) => { - try { - const mod = 'cloudflare:workers' - const { env } = await import(/* @vite-ignore */ mod) - const cfEnv = env as any - const config = useRuntimeConfig(event) + const config = useRuntimeConfig(event) + + const siteUrl = await readEnv('CF_PAGES_URL') + const browserBaseUrl = await readEnv('BROWSER_BASE_URL') + const baseUrl = await readEnv('BASE_URL') + const pyroBaseUrl = await readEnv('PYRO_BASE_URL') + const stripePublishableKey = await readEnv('STRIPE_PUBLISHABLE_KEY') - if (cfEnv.CF_PAGES_URL) config.public.siteUrl = cfEnv.CF_PAGES_URL - if (cfEnv.BROWSER_BASE_URL) config.public.apiBaseUrl = cfEnv.BROWSER_BASE_URL - if (cfEnv.BASE_URL) config.apiBaseUrl = cfEnv.BASE_URL - if (cfEnv.PYRO_BASE_URL) { - config.public.pyroBaseUrl = cfEnv.PYRO_BASE_URL - config.pyroBaseUrl = cfEnv.PYRO_BASE_URL - } - if (cfEnv.STRIPE_PUBLISHABLE_KEY) - config.public.stripePublishableKey = cfEnv.STRIPE_PUBLISHABLE_KEY - } catch { - /* empty */ + if (siteUrl) config.public.siteUrl = siteUrl + if (browserBaseUrl) config.public.apiBaseUrl = browserBaseUrl + if (baseUrl) config.apiBaseUrl = baseUrl + if (pyroBaseUrl) { + config.public.pyroBaseUrl = pyroBaseUrl + config.pyroBaseUrl = pyroBaseUrl } + if (stripePublishableKey) config.public.stripePublishableKey = stripePublishableKey }) diff --git a/apps/frontend/src/server/routes/api/intercom/messenger-jwt.get.ts b/apps/frontend/src/server/routes/api/intercom/messenger-jwt.get.ts index 25bc71d63c..398a72b2f5 100644 --- a/apps/frontend/src/server/routes/api/intercom/messenger-jwt.get.ts +++ b/apps/frontend/src/server/routes/api/intercom/messenger-jwt.get.ts @@ -1,23 +1,13 @@ import { type Labrinth, ModrinthApiError } from '@modrinth/api-client' import { SignJWT } from 'jose' +import { readEnv } from '~/helpers/env' import { useServerModrinthClient } from '~/server/utils/api-client' type IntercomTokenResponse = { token: string } -async function getIntercomKeyFromSecretsStore(): Promise { - try { - const mod = 'cloudflare:workers' - const { env } = await import(/* @vite-ignore */ mod) - return await env.INTERCOM_IDENTITY_SECRET?.get() - } catch { - // Not running in Cloudflare Workers environment - return undefined - } -} - async function signIntercomUserJwt( user: { id: string; username: string; email?: string; created: string }, secret: string, @@ -72,7 +62,7 @@ export default defineEventHandler(async (event): Promise setHeader(event, 'cache-control', 'private, no-store, max-age=0') const intercomSecret = - (await getIntercomKeyFromSecretsStore()) ?? useRuntimeConfig(event).intercomIdentitySecret + (await readEnv('INTERCOM_IDENTITY_SECRET')) ?? useRuntimeConfig(event).intercomIdentitySecret if (!intercomSecret) { throw createError({ diff --git a/apps/frontend/src/server/utils/api-client.ts b/apps/frontend/src/server/utils/api-client.ts index a3b3101c3e..9350b05cef 100644 --- a/apps/frontend/src/server/utils/api-client.ts +++ b/apps/frontend/src/server/utils/api-client.ts @@ -7,18 +7,9 @@ import { } from '@modrinth/api-client' import { getRequestHeader, type H3Event } from 'h3' +import { readEnv } from '~/helpers/env' import { getFrontendUserAgent, VISITOR_USER_AGENT_HEADER } from '~/helpers/user-agent' -async function getRateLimitKeyFromSecretsStore(): Promise { - try { - const mod = 'cloudflare:workers' - const { env } = await import(/* @vite-ignore */ mod) - return await env.RATE_LIMIT_IGNORE_KEY?.get() - } catch { - return undefined - } -} - export interface ServerModrinthClientOptions { event?: H3Event authToken?: string @@ -50,7 +41,7 @@ export function useServerModrinthClient(options?: ServerModrinthClientOptions): sharedInstancesBaseUrl, userAgent: getFrontendUserAgent(config.public.hash), headers: visitorUserAgent ? { [VISITOR_USER_AGENT_HEADER]: visitorUserAgent } : undefined, - rateLimitKey: config.rateLimitKey || getRateLimitKeyFromSecretsStore, + rateLimitKey: config.rateLimitKey || (() => readEnv('RATE_LIMIT_IGNORE_KEY')), features, } From 906f132db50e20853faaf283d2b2c5055f01e1af Mon Sep 17 00:00:00 2001 From: "Michael H." Date: Thu, 20 Aug 2026 17:30:08 +0200 Subject: [PATCH 6/7] build: remove this pr branch test --- .github/workflows/frontend-docker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/frontend-docker.yml b/.github/workflows/frontend-docker.yml index c8ddc97a30..484f13125c 100644 --- a/.github/workflows/frontend-docker.yml +++ b/.github/workflows/frontend-docker.yml @@ -3,7 +3,6 @@ name: Frontend Docker on: push: branches: - - michael/frontend-docker - main - prod paths: From d5d840bdc55452aa192acd07a99c0b7ea613d99d Mon Sep 17 00:00:00 2001 From: "Michael H." Date: Thu, 20 Aug 2026 17:35:25 +0200 Subject: [PATCH 7/7] fix: don't pin docker image ver --- apps/frontend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile index 484d991b7a..439ea2fefa 100644 --- a/apps/frontend/Dockerfile +++ b/apps/frontend/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM node:24.15.0-slim +FROM node:24-slim LABEL org.opencontainers.image.source=https://github.com/modrinth/code LABEL org.opencontainers.image.title=frontend