101 lines
3.9 KiB
Markdown
101 lines
3.9 KiB
Markdown
# Same SPA Build, Different Behavior Per Domain
|
|
|
|
## Scenario
|
|
|
|
Same build (single `dist/` folder, same nginx server block) serves multiple domains. Feature works on domain A but fails on domain B with a Zod validation error like "Invalid input." No code or API differences exist.
|
|
|
|
## Investigation Checklist
|
|
|
|
Before touching any code:
|
|
|
|
```
|
|
Build layer
|
|
├── Same dist/ folder? → check nginx `root` directive
|
|
└── Same .env baked in? → check VITE_ prefixed vars at build time
|
|
|
|
Runtime layer
|
|
├── Same API instance? → check proxy_pass targets
|
|
├── Same user data? → query the API for same record
|
|
└── Same auth token? → check cookies/localStorage
|
|
|
|
Client layer
|
|
├── localStorage (per origin) ← MOST COMMON CULPRIT
|
|
├── sessionStorage (per tab)
|
|
├── IndexedDB (per origin)
|
|
├── Service worker cache (per origin)
|
|
└── Cookies (per origin + path)
|
|
```
|
|
|
|
## Common Root Cause: Stale Persisted Client State
|
|
|
|
App version N persisted a specific state shape to localStorage (Zustand `persist` middleware, Redux `persist`, or raw `localStorage.setItem`). Version N+1 changed the schema — added required fields, changed enum values, removed fields. The browser on domain B still has the old shape rehydrated, and it fails Zod validation.
|
|
|
|
### Diagnosis Steps
|
|
|
|
**1. Confirm code is identical across domains:**
|
|
|
|
```bash
|
|
# Check nginx config — same root for all server_names?
|
|
cat /etc/nginx/sites-enabled/your-site | grep -E "server_name|root "
|
|
```
|
|
|
|
**2. Check API — same backend?**
|
|
|
|
Same `proxy_pass` target for all domains.
|
|
|
|
**3. Trace the validation path in the failing handler:**
|
|
|
|
```ts
|
|
// Look for schema validation like this:
|
|
const parsed = quoteWriteSchema.safeParse(data);
|
|
if (!parsed.success) {
|
|
console.warn('Validation failed:', JSON.stringify(parsed.error.issues, null, 2));
|
|
showToast(parsed.error.issues[0].message, 'error');
|
|
}
|
|
```
|
|
|
|
**4. On the failing domain, open DevTools Console → trigger the action.** Look for the validation warning — it names the exact field that's failing:
|
|
|
|
```
|
|
Quote schema validation failed: [{"code":"invalid_enum_value","path":["discountType"],...}]
|
|
```
|
|
|
|
**5. Check localStorage for the persisted state key:**
|
|
|
|
DevTools → Application → Local Storage → `failing-domain.com` → find the Zustand/Redux persist key (e.g., `spq-quote`).
|
|
|
|
### Fields Most Likely to Fail with Stale State
|
|
|
|
| Field type | Schema rule | Failure symptom |
|
|
|------------|-------------|-----------------|
|
|
| `z.enum([...])` | Required enum, no default | "Invalid enum value" / "Invalid input" |
|
|
| `z.string().min(1)` | Required non-empty | "String must contain at least 1 character(s)" |
|
|
| `z.number()` | Expected number, was string | "Expected number, received string" |
|
|
| `z.array().min(1)` | Non-empty array | "At least one service is required" |
|
|
| Nested object | New field added post-release | "Required" on the new field |
|
|
|
|
### Fix (Short-term)
|
|
|
|
Clear localStorage for the failing domain:
|
|
|
|
```js
|
|
localStorage.removeItem('spq-quote') // or whatever the persist key is
|
|
```
|
|
|
|
### Fix (Long-term — code)
|
|
|
|
Make the schema forward-compatible so old persisted data doesn't fail:
|
|
|
|
- Use `.optional().default('')` or `.optional().default(someValue)` on fields added after initial release
|
|
- Add a migration in the `persist` middleware's `onRehydrateStorage` callback
|
|
- Strip nulls and fill defaults before Zod validation (like `sanitizeServices()`)
|
|
- Use `z.union([z.string(), z.undefined()]).optional().default('')` for fields that may be missing
|
|
|
|
## Anti-patterns
|
|
|
|
- **Modifying source code thinking domains have different builds** — always check nginx first
|
|
- **Adding environment-specific toggles** — the code IS identical, the difference is client-side
|
|
- **Blaming the API** — both domains hit the same backend instance
|
|
|
|
The correct first step: check nginx config → confirm same build → check localStorage on both domains → reproduce the validation failure in the console → read the Zod error path to identify which field is stale.
|