diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh
index 2802e02..4a17e89 100755
--- a/.claude/hooks/session-start.sh
+++ b/.claude/hooks/session-start.sh
@@ -18,7 +18,7 @@ cd "${CLAUDE_PROJECT_DIR:-$(dirname "$0")/../..}"
# script only runs where .tool-versions isn't guaranteed to be self-enforcing
# (see the CLAUDE_CODE_REMOTE guard above). On Node 18, pnpm silently skips
# the platform-specific @oxc-parser native bindings; the install still exits
-# 0 and the failure only appears later as an unrelated-looking `astro check`
+# 0 and the failure only appears later as an unrelated-looking `astro sync`
# crash.
REQUIRED_NODE_MAJOR="$(awk '$1 == "nodejs" { split($2, v, "."); print v[1] }' .tool-versions 2>/dev/null)"
REQUIRED_NODE_MAJOR="${REQUIRED_NODE_MAJOR:-22}"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2e40705..d584c84 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -48,7 +48,7 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- # `pnpm run build` is `astro check && astro build`, which never invokes
+ # `pnpm run build` is type-check + `astro build`, which never invokes
# ESLint or Prettier. Without these two steps a change that breaks
# linting outright — an ESLint major that orphans the config file, say —
# still reports a green build.
diff --git a/CLAUDE.md b/CLAUDE.md
index 3fcaba7..d6c1dca 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -39,7 +39,7 @@ Cannot find module '@oxc-parser/binding-linux-x64-gnu'
```
Because that happens inside Astro's content-collection sync, the visible
-symptom is `astro check` aborting about two seconds in — which reads like a
+symptom is `astro sync` aborting about two seconds in — which reads like a
config or content error, not a dependency error.
Quick check that your install is intact:
@@ -57,12 +57,12 @@ ls node_modules/@oxc-parser/ # must NOT be empty
|---|---|
| `pnpm install --frozen-lockfile` | Install exactly what the lockfile specifies |
| `pnpm run dev` | Dev server at `localhost:4321` |
-| `pnpm run build` | **`astro check && astro build`** — type errors fail the build |
+| `pnpm run build` | **`astro:check` then `astro build`** — type errors fail the build |
| `pnpm run preview` | Serve the production build locally |
| `pnpm run astro:check` | Type-check only, no build |
| `pnpm run lint` / `lint:fix` | ESLint |
| `pnpm run format` / `format:check` | Prettier, plus straightening curly quotes in Markdown |
-| `pnpm run check` | `astro check` + lint + format check |
+| `pnpm run check` | `astro:check` + lint + format check |
| `pnpm run check:fix` | Same, but writes fixes |
| `pnpm new:post` | Scaffold a blog post (interactive, or `--title "..."`; `--help` for flags) |
@@ -167,9 +167,12 @@ font from a third-party demo host at module scope, which made every production
build depend on an unrelated service's uptime. Fonts now come from
`public/fonts/`. Don't reintroduce build-time fetches — vendor the asset.
-**`astro check` gates the build.** `pnpm run build` runs it first, so a type
-error anywhere blocks deployment. Run `pnpm run astro:check` while iterating for
-a faster loop.
+**Type-checking gates the build.** `pnpm run build` runs `astro:check` first, so
+a type error anywhere blocks deployment. That script is `astro sync && astro-check`,
+not `astro check`: when `@astrojs/check` fails to import, `astro check` logs an
+error and still exits 0, so a broken install silently skips type-checking.
+`astro-check` crashes instead. Keep the `astro sync` — without it a fresh checkout
+reports false errors.
**OG images are generated at build time** by satori + resvg for every post.
Changing the OG templates re-renders ~215 PNGs, so image-heavy changes are slow
@@ -201,6 +204,12 @@ anything.
Vercel picks it up. It deliberately does not deploy; a previous version tried to
and produced a second, competing deploy path.
+Vercel's `installCommand` deletes `node_modules` before a frozen install. Vercel
+restores `node_modules` from its build cache, and pnpm keeps a record there of
+optional packages it skipped; when a lockfile change makes one of those
+required, an incremental install still skips it. That left `@emnapi/runtime`
+missing after Astro 7.4, so `@astrojs/check` could not load.
+
If you touch `vercel.json`, remember this is a **static multi-page** site that
emits real per-route HTML — not an SPA. It does not want a catch-all rewrite to
`/index.html`.
diff --git a/README.md b/README.md
index e83bb8f..96c51d8 100644
--- a/README.md
+++ b/README.md
@@ -86,13 +86,13 @@ Based on the original Brutal theme with our enhancements:
| Command | Action | Notes |
|---------|--------|-------|
| `pnpm run dev` | Start local dev server at `localhost:4321` | Enhanced with TypeScript support |
-| `pnpm run build` | Build production site to `./dist/` | Runs `astro check` first — type errors fail the build |
+| `pnpm run build` | Build production site to `./dist/` | Runs `astro:check` first — type errors fail the build |
| `pnpm run preview` | Preview build locally before deploying | Test production build |
| `pnpm run astro` | Run Astro CLI commands | Full CLI access |
| `pnpm run astro:check` | Type-check without building | Faster iteration loop |
| `pnpm run lint` / `lint:fix` | ESLint | `:fix` writes fixes |
| `pnpm run format` / `format:check` | Prettier | `format:check` is read-only |
-| `pnpm run check` / `check:fix` | `astro check` + lint + format | Full pre-commit sweep |
+| `pnpm run check` / `check:fix` | `astro:check` + lint + format | Full pre-commit sweep |
| `pnpm new:post` | Create a new blog post with frontmatter | Interactive, or pass `--title`; `--help` for flags |
A full build takes roughly 70-110 seconds and emits ~215 pages, including a
@@ -255,7 +255,7 @@ GitHub repository, builds on every push to `main`, and publishes to
#### **GitHub Actions is a build gate, not a deploy path**
`.github/workflows/ci.yml` installs dependencies and runs `pnpm run build`
-(`astro check && astro build`) on pushes and pull requests to `main`. That's
+(type-check, then `astro build`) on pushes and pull requests to `main`. That's
all it does. If a change doesn't build, it fails here before Vercel picks it up.
An earlier version of this workflow also deployed to Vercel via
@@ -302,7 +302,7 @@ graph TD
A[Push to main] --> B[GitHub Actions: ci.yml]
C[Open Pull Request] --> B
B --> D[pnpm install --frozen-lockfile]
- D --> E[pnpm run build
astro check && astro build]
+ D --> E[pnpm run build
astro sync, astro-check, astro build]
E --> F{Build passes?}
F -->|No| G[Red check, stop]
F -->|Yes| H[Green check]
diff --git a/package.json b/package.json
index 13b7f45..273bc7e 100644
--- a/package.json
+++ b/package.json
@@ -12,17 +12,17 @@
"scripts": {
"dev": "astro dev",
"start": "astro dev",
- "build": "astro check && astro build",
+ "build": "pnpm run astro:check && astro build",
"preview": "astro preview",
"astro": "astro",
"new:post": "node scripts/new-post.js",
- "astro:check": "astro check",
+ "astro:check": "astro sync && astro-check",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
- "check": "astro check && pnpm run lint && pnpm run format:check",
- "check:fix": "astro check && pnpm run lint:fix && pnpm run format"
+ "check": "pnpm run astro:check && pnpm run lint && pnpm run format:check",
+ "check:fix": "pnpm run astro:check && pnpm run lint:fix && pnpm run format"
},
"dependencies": {
"@astrojs/rss": "^4.0.19",
diff --git a/vercel.json b/vercel.json
index b687993..b5fd4f9 100644
--- a/vercel.json
+++ b/vercel.json
@@ -2,7 +2,7 @@
"buildCommand": "pnpm run build",
"outputDirectory": "dist",
"framework": "astro",
- "installCommand": "pnpm install",
+ "installCommand": "rm -rf node_modules && pnpm install --frozen-lockfile",
"devCommand": "pnpm run dev",
"redirects": [
{