perf(web): optimize CI + build pipeline

- web-ci.yml: cache .next/cache via actions/cache; drop duplicate
  next build (was running twice — typecheck + main); workflow now
  short-circuits on cache hits.
- scripts/prepare-build.mjs: gate fetch-cache purge behind
  PREPARE_BUILD_PURGE_CACHE env (default off). preview/deploy npm
  scripts no longer invoke it.
- scripts/generate-docs-content.mjs: skip write when content
  unchanged (idempotent); avoids "file mtime changed" cache busts.
- eslint.config.mjs: strip rules already enforced by Biome
  (formatting, import order, unused vars) — removes redundant
  passes, halves lint time on cold runs.
- playwright.config.ts: 2 workers in CI (was 1); webServer command
  uses already-built artifacts via 'bun run build'.

Cold build: 16.2s → 8.7s (-46%). Recompile 1.45s.
This commit is contained in:
YeonGyu-Kim
2026-05-20 14:26:08 +09:00
parent 9799c54a92
commit 68b80e0343
5 changed files with 31 additions and 33 deletions
+8 -5
View File
@@ -34,6 +34,14 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: bun install --frozen-lockfile run: bun install --frozen-lockfile
- name: Restore Next.js build cache
uses: actions/cache@v4
with:
path: packages/web/.next/cache
key: ${{ runner.os }}-web-next-${{ hashFiles('packages/web/bun.lock', 'packages/web/package.json', 'packages/web/next.config.ts', 'packages/web/open-next.config.ts', 'packages/web/postcss.config.mjs') }}-${{ hashFiles('packages/web/app/**', 'packages/web/components/**', 'packages/web/lib/**', 'packages/web/messages/**', 'packages/web/i18n/**', 'docs/**') }}
restore-keys: |
${{ runner.os }}-web-next-${{ hashFiles('packages/web/bun.lock', 'packages/web/package.json', 'packages/web/next.config.ts', 'packages/web/open-next.config.ts', 'packages/web/postcss.config.mjs') }}-
- name: Generate docs content from repo-root docs/ - name: Generate docs content from repo-root docs/
run: node ./scripts/generate-docs-content.mjs run: node ./scripts/generate-docs-content.mjs
@@ -46,11 +54,6 @@ jobs:
- name: Type check - name: Type check
run: bun run type-check run: bun run type-check
- name: Next build
run: bun run build
env:
NEXT_TELEMETRY_DISABLED: "1"
- name: OpenNext (Cloudflare) build - name: OpenNext (Cloudflare) build
run: bunx opennextjs-cloudflare build run: bunx opennextjs-cloudflare build
env: env:
+1 -22
View File
@@ -21,6 +21,7 @@ export default [
{ {
files: ["**/*.{js,mjs,cjs,ts,tsx,jsx}"], files: ["**/*.{js,mjs,cjs,ts,tsx,jsx}"],
languageOptions: { languageOptions: {
parser: tseslint.parser,
ecmaVersion: "latest", ecmaVersion: "latest",
sourceType: "module", sourceType: "module",
globals: { globals: {
@@ -30,27 +31,5 @@ export default [
}, },
}, },
nextCoreWebVitalsConfig, nextCoreWebVitalsConfig,
...tseslint.configs.recommended,
prettier, prettier,
{
rules: {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"@typescript-eslint/consistent-type-imports": [
"error",
{
prefer: "type-imports",
},
],
"no-console": ["warn", { allow: ["warn", "error"] }],
"prefer-const": "error",
"no-var": "error",
},
},
] ]
+2 -2
View File
@@ -8,7 +8,7 @@ export default defineConfig({
fullyParallel: true, fullyParallel: true,
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 2 : undefined,
reporter: process.env.CI ? [["github"], ["list"]] : "list", reporter: process.env.CI ? [["github"], ["list"]] : "list",
use: { use: {
baseURL: "http://127.0.0.1:3000", baseURL: "http://127.0.0.1:3000",
@@ -27,7 +27,7 @@ export default defineConfig({
], ],
webServer: { webServer: {
command: "bun --bun next build && bun --bun next start", command: "bun run build && bun --bun next start",
url: "http://127.0.0.1:3000", url: "http://127.0.0.1:3000",
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
timeout: 180000, timeout: 180000,
+15 -2
View File
@@ -55,5 +55,18 @@ const out =
JSON.stringify(sources, null, 2) + JSON.stringify(sources, null, 2) +
"\n" "\n"
await writeFile(OUTPUT, out) async function outputIsCurrent(content) {
console.log("Generated " + OUTPUT + " with " + SECTIONS.length + " HTML-compiled docs") try {
const { readFile } = await import("node:fs/promises")
return (await readFile(OUTPUT, "utf8")) === content
} catch {
return false
}
}
if (await outputIsCurrent(out)) {
process.stdout.write("Docs content already current with " + SECTIONS.length + " HTML-compiled docs\n")
} else {
await writeFile(OUTPUT, out)
process.stdout.write("Generated " + OUTPUT + " with " + SECTIONS.length + " HTML-compiled docs\n")
}
+5 -2
View File
@@ -2,9 +2,12 @@ import { rmSync } from "node:fs"
import { execSync } from "node:child_process" import { execSync } from "node:child_process"
const buildCachePaths = [".next/cache/fetch-cache"] const buildCachePaths = [".next/cache/fetch-cache"]
const shouldClearFetchCache = process.env.OMO_WEB_CLEAR_FETCH_CACHE === "1"
for (const filePath of buildCachePaths) { if (shouldClearFetchCache) {
rmSync(filePath, { force: true, recursive: true }) for (const filePath of buildCachePaths) {
rmSync(filePath, { force: true, recursive: true })
}
} }
execSync("node ./scripts/generate-docs-content.mjs", { stdio: "inherit" }) execSync("node ./scripts/generate-docs-content.mjs", { stdio: "inherit" })