build size reduse it
This commit is contained in:
48
.dockerignore
Normal file
48
.dockerignore
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Docker build context excludes.
|
||||||
|
#
|
||||||
|
# WITHOUT this file, the `COPY . .` in the builder stage ships the host's
|
||||||
|
# .next/ (2.1 GB, almost all of it the Turbopack dev cache) and node_modules/
|
||||||
|
# (639 MB, with darwin-arm64 sharp binaries that are wrong for Alpine) into
|
||||||
|
# the build context. That is what filled the production disk.
|
||||||
|
|
||||||
|
# Dependencies — reinstalled from the lockfile in the deps stage
|
||||||
|
node_modules
|
||||||
|
.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn
|
||||||
|
|
||||||
|
# Build output — regenerated by `npm run build` in the builder stage.
|
||||||
|
# .next/dev alone is 1.8 GB of dev-server cache that must never leave the host.
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
|
||||||
|
# VCS + local tooling
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.github
|
||||||
|
.claude
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Incremental compiler state (253 KB and host-specific)
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# Secrets — injected at runtime, never baked into a layer
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# Docs and infra that the build does not read
|
||||||
|
*.md
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
nginx.conf
|
||||||
|
|
||||||
|
# Noise
|
||||||
|
.DS_Store
|
||||||
|
coverage
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
/dist
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
28
AGENTS.md
28
AGENTS.md
@@ -83,6 +83,34 @@ Gray is the accent. The **only** colour permitted is semantic: success, warning,
|
|||||||
- `StyleOverrides` supports structural pseudo-classes, not just interaction ones —
|
- `StyleOverrides` supports structural pseudo-classes, not just interaction ones —
|
||||||
`':first-child'` emits correctly (used for the table first-column lead-in).
|
`':first-child'` emits correctly (used for the table first-column lead-in).
|
||||||
|
|
||||||
|
## Deployment — never ship `.next/` wholesale
|
||||||
|
This filled the production disk and took the server down once. A working tree's `.next/`
|
||||||
|
reaches **2+ GB**, but almost none of it is runtime state:
|
||||||
|
|
||||||
|
| path | size (typical) | ships? |
|
||||||
|
|---|---|---|
|
||||||
|
| `.next/dev` | 1.8 GB | **no** — Turbopack dev-server cache from `npm run dev` |
|
||||||
|
| `.next/cache` | 120–160 MB | **no** — incremental build cache, build host only |
|
||||||
|
| `.next/server`, `types`, `trace` | ~24 MB | **no** — inputs to the standalone trace, not read at runtime |
|
||||||
|
| `.next/standalone` | 54 MB | **yes** — server.js + traced node_modules |
|
||||||
|
| `.next/static` | 3 MB | **yes** — hashed client assets (nginx serves at `/_next/static/`) |
|
||||||
|
| `public` | 344 KB | **yes** |
|
||||||
|
|
||||||
|
Those last three are the entire payload: **~55 MB unpacked, 18 MB gzipped.**
|
||||||
|
- Non-Docker deploy: `npm run bundle` → `dist/loyaly-mer-login.tar.gz`. Never `scp -r .next`.
|
||||||
|
- Docker: `.dockerignore` excludes `node_modules` and `.next`. **Do not delete it** — without
|
||||||
|
it `COPY . .` pushes the host's 2 GB `.next` and 639 MB `node_modules` into the build
|
||||||
|
context, including darwin-arm64 sharp binaries that are wrong for Alpine.
|
||||||
|
- `npm run clean` drops `.next`, `dist` and the tsbuildinfo when the tree gets heavy.
|
||||||
|
|
||||||
|
**`CI_BUILD=1` in container builds.** Next 16.3 enables `turbopackFileSystemCacheForBuild`
|
||||||
|
by default; it writes 117 MB to `.next/cache` that only pays off when that directory is
|
||||||
|
restored between builds. Docker/Dokploy starts from a clean layer every time, so it is
|
||||||
|
written and never read. The Dockerfile sets `CI_BUILD=1`, which flips the flag off in
|
||||||
|
`next.config.ts`: **~20% less build CPU** (32s vs 40–43s measured) and `.next` drops
|
||||||
|
202 MB → 86 MB. Leave it unset locally — repeat `npm run build` there does reuse the cache.
|
||||||
|
It changes no output: the deployable payload is 58 MB either way.
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
- Follow the Astryx rules above: no raw `<div>` for layout, no hardcoded hex/px, component
|
- Follow the Astryx rules above: no raw `<div>` for layout, no hardcoded hex/px, component
|
||||||
props first then token-backed Tailwind utilities.
|
props first then token-backed Tailwind utilities.
|
||||||
|
|||||||
27
Dockerfile
27
Dockerfile
@@ -1,10 +1,14 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
# Stage 1: Install dependencies
|
# Stage 1: Install dependencies
|
||||||
FROM node:22-alpine AS deps
|
FROM node:22-alpine AS deps
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci
|
# devDeps are required to build (typescript, tailwind, eslint-config-next).
|
||||||
|
# This whole stage is discarded — none of it reaches the runner.
|
||||||
|
RUN npm ci --no-audit --no-fund
|
||||||
|
|
||||||
# Stage 2: Build the Next.js application
|
# Stage 2: Build the Next.js application
|
||||||
FROM node:22-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
@@ -14,11 +18,16 @@ COPY . .
|
|||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
# Each Docker build starts from a clean layer, so Turbopack's .next/cache is
|
||||||
|
# written but never restored. Skipping it cuts ~20% of build CPU (the metric
|
||||||
|
# that matters on a 1-vCPU host) and 116 MB off this layer.
|
||||||
|
ENV CI_BUILD=1
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Stage 3: Production runner with Next.js Standalone
|
# Stage 3: Production runner with Next.js Standalone
|
||||||
FROM node:22-alpine AS runner
|
FROM node:22-alpine AS runner
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
@@ -26,10 +35,18 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
|||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
ENV HOSTNAME="0.0.0.0"
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
# Copy public static assets and standalone build output
|
# Run as a non-root user; nextjs owns nothing it does not need to write.
|
||||||
COPY --from=builder /app/public ./public
|
RUN addgroup -g 1001 -S nodejs && adduser -u 1001 -S nextjs -G nodejs
|
||||||
COPY --from=builder /app/.next/standalone ./
|
|
||||||
COPY --from=builder /app/.next/static ./.next/static
|
# Copy public static assets and standalone build output.
|
||||||
|
# These three paths are the ENTIRE runtime payload (~57 MB). Never copy the
|
||||||
|
# whole .next/ directory here — .next/dev and .next/cache are build-host-only
|
||||||
|
# and account for ~1.96 GB.
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const eslintConfig = defineConfig([
|
|||||||
".next/**",
|
".next/**",
|
||||||
"out/**",
|
"out/**",
|
||||||
"build/**",
|
"build/**",
|
||||||
|
// Deploy artifact from `npm run bundle` — traced vendor code, not ours.
|
||||||
|
"dist/**",
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
// Generated by `npm run theme:build` — edit src/theme/loyalyTheme.ts
|
// Generated by `npm run theme:build` — edit src/theme/loyalyTheme.ts
|
||||||
// instead. The emitted .d.ts uses a triple-slash reference we do not own.
|
// instead. The emitted .d.ts uses a triple-slash reference we do not own.
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ import type { NextConfig } from "next";
|
|||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
|
experimental: {
|
||||||
|
// Turbopack's build cache lives in .next/cache and only pays off when that
|
||||||
|
// directory survives between builds. A Docker/Dokploy build starts from a
|
||||||
|
// clean layer every time, so the cache is written and never read — ~117 MB
|
||||||
|
// of pure write cost per build. CI_BUILD is set in the Dockerfile only, so
|
||||||
|
// local `npm run build` keeps its warm cache.
|
||||||
|
turbopackFileSystemCacheForBuild: process.env.CI_BUILD !== "1",
|
||||||
|
},
|
||||||
allowedDevOrigins: ["192.168.0.117", "192.168.0.*", "192.168.1.*", "localhost", "127.0.0.1"],
|
allowedDevOrigins: ["192.168.0.117", "192.168.0.*", "192.168.1.*", "localhost", "127.0.0.1"],
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start -p 3100",
|
"start": "next start -p 3100",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
|
"clean": "rm -rf .next dist tsconfig.tsbuildinfo",
|
||||||
|
"bundle": "bash scripts/bundle.sh",
|
||||||
"theme:build": "astryx theme build src/theme/loyalyTheme.ts",
|
"theme:build": "astryx theme build src/theme/loyalyTheme.ts",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"dev:preview": "next dev"
|
"dev:preview": "next dev"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 391 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB |
BIN
public/lyts.png
BIN
public/lyts.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 385 B |
49
scripts/bundle.sh
Executable file
49
scripts/bundle.sh
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Produce the deployable artifact for a non-Docker deploy.
|
||||||
|
#
|
||||||
|
# Copying .next/ wholesale is what filled the production disk: a working tree's
|
||||||
|
# .next/ reaches 2+ GB, but only three paths are read at runtime —
|
||||||
|
# .next/standalone the server + its traced node_modules
|
||||||
|
# .next/static hashed client assets (served by nginx at /_next/static/)
|
||||||
|
# public unhashed public assets
|
||||||
|
# Everything else (.next/cache, .next/dev, .next/server, .next/types) is
|
||||||
|
# build-host state and must never be shipped.
|
||||||
|
#
|
||||||
|
# Usage: npm run bundle -> dist/loyaly-mer-login.tar.gz
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
OUT="dist"
|
||||||
|
STAGE="$OUT/bundle"
|
||||||
|
|
||||||
|
for p in .next/standalone .next/static public; do
|
||||||
|
if [ ! -d "$p" ]; then
|
||||||
|
echo "error: $p missing — run 'npm run build' first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
rm -rf "$STAGE"
|
||||||
|
mkdir -p "$STAGE"
|
||||||
|
|
||||||
|
# standalone already contains server.js, package.json and traced node_modules,
|
||||||
|
# and expects static/ and public/ to sit beside it in the same layout.
|
||||||
|
cp -R .next/standalone/. "$STAGE/"
|
||||||
|
mkdir -p "$STAGE/.next"
|
||||||
|
cp -R .next/static "$STAGE/.next/static"
|
||||||
|
cp -R public "$STAGE/public"
|
||||||
|
|
||||||
|
TARBALL="$OUT/loyaly-mer-login.tar.gz"
|
||||||
|
rm -f "$TARBALL"
|
||||||
|
tar -czf "$TARBALL" -C "$STAGE" .
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "bundle: $(du -sh "$STAGE" | cut -f1) ($STAGE)"
|
||||||
|
echo "tarball: $(du -sh "$TARBALL" | cut -f1) ($TARBALL)"
|
||||||
|
echo
|
||||||
|
echo "deploy: scp $TARBALL <host>:/srv/ && tar -xzf loyaly-mer-login.tar.gz -C /srv/app"
|
||||||
|
echo "run: PORT=3000 HOSTNAME=0.0.0.0 NODE_ENV=production node server.js"
|
||||||
Reference in New Issue
Block a user