Migration To v4
Breaking changes, migration steps, and why Permix 4 changes SSR and hydration semantics
Why this release is major
Permix 4.0.0 fixes real correctness issues around SSR hydration, multi-role composition, and request isolation in Next.js. Some of those fixes change public types and expectations, so this release is intentionally a major.
Breaking Changes
PermixStateJSON now contains null
Dehydrated function rules are now represented as null instead of being flattened to false.
type Before = boolean
type After = boolean | nullThat means code reading a dehydrated snapshot should now distinguish between:
true: allowedfalse: explicitly deniednull: not serializable, needs live rules to be restored
Hydration readiness is explicit
If the dehydrated snapshot is fully boolean, hydrate() makes the client ready immediately.
If the snapshot contains null, hydration restores only the boolean shell. Call setup() after hydrate() to restore the live function rules:
permix.hydrate(state)
permix.setup(liveRules)Next.js guidance changed
If you previously used one mutable server singleton and repeatedly called setup() in the App Router, migrate away from that pattern. Use request-scoped snapshots or isolated instances instead.
Migration Steps
1. Update snapshot handling code
If you store or inspect dehydrated state, update its type assumptions:
import type { PermixStateJSON } from '@aminzoubaa/permix'
function isCompleteSnapshot(state: PermixStateJSON<any>) {
return Object.values(state).every(entity =>
Object.values(entity).every(value => value !== null),
)
}2. Restore function rules after client hydration
If your rules contain predicates, keep the server authoritative and restore the client rules explicitly when needed:
'use client'
import { createPermix } from '@aminzoubaa/permix'
import { PermixHydrate, PermixProvider } from '@aminzoubaa/permix/react'
import { useEffect, useState } from 'react'
const [permix] = useState(() => createPermix<Definition>())
useEffect(() => {
permix.setup(liveRules)
}, [permix, liveRules])
return (
<PermixProvider permix={permix}>
<PermixHydrate state={state}>{children}</PermixHydrate>
</PermixProvider>
)3. Replace single-role selection with merged rules
If a user can have several roles, compose them before setup:
import { mergeRules } from '@aminzoubaa/permix'
permix.setup(mergeRules(authorRules, reviewerRules, supportRules))4. Replace shared server singletons in Next.js
Old pattern:
export const permix = createPermix<Definition>()
export async function getPermissions() {
permix.setup(await loadRules())
return permix
}Recommended pattern:
import { createPermixSnapshot } from '@aminzoubaa/permix/next'
export async function getPermissionsSnapshot() {
return createPermixSnapshot(await loadRules())
}Or, if you already have a base instance and need isolation:
const requestPermix = permix.resolve(await loadRules())Benefits after migrating
- Boolean hydration no longer gets stuck behind a loading state.
- Multiple roles can express a real union of capabilities.
- Next.js server requests stop sharing mutable permission state.
- Cache invalidation flows are easier to reason about because the server snapshot is explicit.