diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 1e679b8f..ec1a6183 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -60,18 +60,6 @@ jobs: node-version: 24 cache: pnpm - # Turbopack persists its build cache here (turbopackFileSystemCacheForBuild). - # The key is deterministic (no commit SHA) so every PR restores the same - # entry — in practice the one `main` last saved, which is the only cache - # sibling PRs can all see. - - name: cache Turbopack build cache - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 - with: - path: apps/readest-app/.next/cache - key: turbo-build-web-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - turbo-build-web-${{ runner.os }}- - - name: install Dependencies working-directory: apps/readest-app run: | diff --git a/Dockerfile b/Dockerfile index c83788cc..2ffab4bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,15 +45,27 @@ COPY --from=dependencies /app/apps/readest-app/public/vendor /app/apps/readest-a COPY --from=dependencies /app/packages/foliate-js/node_modules /app/packages/foliate-js/node_modules COPY . . WORKDIR /app/apps/readest-app +# Opt into the self-contained `.next/standalone` tree for this image only; +# next.config.mjs gates `output: 'standalone'` on BUILD_STANDALONE so other +# web builds keep their default output. +ENV BUILD_STANDALONE=true RUN pnpm build-web +# Production runtime ships only the standalone server, its traced node_modules, +# and the static/public assets — no pnpm, no source tree, no dev dependencies, +# no build cache. `output: 'standalone'` (next.config.mjs) emits the self-contained +# tree under .next/standalone, so the entrypoint is a plain `node server.js`. FROM docker.io/library/node:24-slim@sha256:24dc26ef1e3c3690f27ebc4136c9c186c3133b25563ae4d7f0692e4d1fe5db0e AS production-stage -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable -RUN corepack prepare pnpm@11.1.1 --activate +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 WORKDIR /app -COPY --from=build /app /app -WORKDIR /app/apps/readest-app -ENTRYPOINT ["pnpm", "start-web", "-H", "0.0.0.0"] +# Monorepo-rooted standalone tree: server.js + hoisted, traced node_modules. +COPY --from=build --chown=node:node /app/apps/readest-app/.next/standalone ./ +# Static and public assets are not part of the standalone trace; copy them next +# to the server so their default relative paths resolve. +COPY --from=build --chown=node:node /app/apps/readest-app/.next/static ./apps/readest-app/.next/static +COPY --from=build --chown=node:node /app/apps/readest-app/public ./apps/readest-app/public +USER node EXPOSE 3000 +ENTRYPOINT ["node", "apps/readest-app/server.js"] diff --git a/apps/readest-app/next.config.mjs b/apps/readest-app/next.config.mjs index cad5ea63..7412b1f4 100644 --- a/apps/readest-app/next.config.mjs +++ b/apps/readest-app/next.config.mjs @@ -14,12 +14,23 @@ if (isDev) { } const exportOutput = appPlatform !== 'web' && !isDev; +// Opt-in standalone output, set only by the Docker production build +// (Dockerfile). Every other path keeps the original behavior: Tauri `export`, +// local `build-web` (output undefined), dev, and the Cloudflare/OpenNext +// deploy — which forces standalone itself via NEXT_PRIVATE_STANDALONE. +const standaloneOutput = !exportOutput && process.env['BUILD_STANDALONE'] === 'true'; /** @type {import('next').NextConfig} */ const nextConfig = { // Ensure Next.js uses SSG instead of SSR // https://nextjs.org/docs/pages/building-your-application/deploying/static-exports - output: exportOutput ? 'export' : undefined, + // The Docker production image opts into a self-contained `.next/standalone` + // tree (see Dockerfile) so it can ship only the traced runtime; all other + // web builds fall back to the default server output. + output: exportOutput ? 'export' : standaloneOutput ? 'standalone' : undefined, + // Monorepo: trace from the repo root so workspace packages land in the + // standalone tree. Only relevant to — and only set for — the Docker build. + outputFileTracingRoot: standaloneOutput ? path.join(__dirname, '../../') : undefined, pageExtensions: exportOutput ? ['jsx', 'tsx'] : ['js', 'jsx', 'ts', 'tsx'], // Note: This feature is required to use the Next.js Image component in SSG mode. // See https://nextjs.org/docs/messages/export-image-api for different workarounds. @@ -28,11 +39,11 @@ const nextConfig = { }, devIndicators: false, experimental: { - // Persist Turbopack's compilation cache to `.next/` so CI can restore it - // between runs. Dev caching is on by default since Next 16.1; build - // caching is opt-in (beta). + // Dev caching is on by default since Next 16.1. We deliberately do NOT + // enable Turbopack's build cache (turbopackFileSystemCacheForBuild, beta): + // a build interrupted mid-compile leaves a partial cache that the next + // build mishandles, fanning out workers until it exhausts RAM. turbopackFileSystemCacheForDev: true, - turbopackFileSystemCacheForBuild: true, }, // Configure assetPrefix or else the server won't properly resolve your assets. assetPrefix: '',