Passkeys and WebAuthn in Production: Passwordless Authentication, Device Credential Management, and Migration Strategies
A practical guide to WebAuthn registration and authentication ceremonies in TypeScript, credential storage and lifecycle management, cross-device hybrid transport, conditional UI for passkey autofill, migrating password users to passkeys, and the real production challenges of device-bound credential management.
Password authentication has a well-understood failure mode: users reuse passwords, those passwords get breached somewhere in the ecosystem, and credential stuffing attacks exploit that reuse at scale. Phishing compounds this: even unique, strong passwords can be stolen if the user types them into a convincing fake login page.
Passkeys solve both problems structurally. The private key never leaves the device. Credentials are scoped to a specific origin (your exact domain), which makes phishing attacks physically impossible: a passkey registered on app.yourco.com cannot be used on app-yourco.com. Authentication is a cryptographic challenge-response where the authenticator signs a server-generated challenge with the private key, the server verifies with the stored public key, and nothing reusable crosses the network.
The specification behind passkeys is WebAuthn (Web Authentication API), a W3C standard that defines the browser APIs and the underlying protocol. Passkeys, as a term, refer to multi-device credentials: WebAuthn credentials that sync across devices via platform cloud services (iCloud Keychain, Google Password Manager, or compatible password managers). Device-bound credentials, which do not sync, are the older FIDO2 model and still supported.
This article covers the complete implementation: registration and authentication ceremonies in TypeScript, credential storage and management, cross-device authentication with the hybrid transport, conditional UI for passkey autofill, and what a migration from passwords actually looks like.
The WebAuthn Ceremony Structure
WebAuthn has two ceremonies: registration (creating a credential) and authentication (using it). Both follow the same pattern: your server generates a challenge, the browser calls a platform API, the authenticator performs a cryptographic operation, and your server verifies the result.
The ceremony involves three principals:
- Relying Party (RP): your server. Identified by
rpId(typically your domain) andrpName(a human-readable label). - Authenticator: the device or platform credential manager that holds private keys.
- Client: the browser, which mediates between the RP and authenticator.
Registration: Creating a Credential
The registration ceremony creates a new key pair on the authenticator. The public key is sent to your server for storage; the private key never leaves the authenticator.
// lib/webauthn/registration.ts
import { randomBytes } from 'crypto'
export interface RegistrationChallenge {
challenge: string // base64url encoded
userId: string
userName: string
userDisplayName: string
rpId: string
rpName: string
timeout: number
excludeCredentials: ExcludeCredential[]
}
export interface ExcludeCredential {
id: string // base64url encoded credential ID
type: 'public-key'
transports?: AuthenticatorTransport[]
}
export function generateRegistrationOptions(
userId: string,
userName: string,
userDisplayName: string,
existingCredentials: ExcludeCredential[] = []
): RegistrationChallenge {
return {
challenge: randomBytes(32).toString('base64url'),
userId,
userName,
userDisplayName,
rpId: process.env.RP_ID!, // e.g. "yourapp.com"
rpName: process.env.RP_NAME!, // e.g. "Your App"
timeout: 60000,
excludeCredentials: existingCredentials,
}
}
Store the challenge server-side (keyed to the session or a short-lived token) before sending it to the browser. The challenge is single-use and must be verified on completion.
On the browser, pass the server options to navigator.credentials.create():
// browser/passkey-register.ts
import type { RegistrationChallenge } from '../lib/webauthn/registration'
function base64urlToBuffer(b64url: string): ArrayBuffer {
const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/')
const binary = atob(b64)
const buffer = new ArrayBuffer(binary.length)
const view = new Uint8Array(buffer)
for (let i = 0; i < binary.length; i++) {
view[i] = binary.charCodeAt(i)
}
return buffer
}
function bufferToBase64url(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
let binary = ''
for (const b of bytes) binary += String.fromCharCode(b)
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
}
export async function registerPasskey(
opts: RegistrationChallenge
): Promise<PublicKeyCredential> {
const credential = await navigator.credentials.create({
publicKey: {
challenge: base64urlToBuffer(opts.challenge),
rp: { id: opts.rpId, name: opts.rpName },
user: {
id: base64urlToBuffer(opts.userId),
name: opts.userName,
displayName: opts.userDisplayName,
},
pubKeyCredParams: [
{ type: 'public-key', alg: -7 }, // ES256 (P-256)
{ type: 'public-key', alg: -257 }, // RS256 (RSA-PKCS1-v1_5)
],
authenticatorSelection: {
residentKey: 'required', // Required for passkeys
requireResidentKey: true,
userVerification: 'required', // Biometric or PIN required
},
excludeCredentials: opts.excludeCredentials.map((c) => ({
id: base64urlToBuffer(c.id),
type: c.type,
transports: c.transports,
})),
attestation: 'none', // For passkeys, attestation adds complexity with no benefit
timeout: opts.timeout,
},
}) as PublicKeyCredential
return credential
}
residentKey: 'required' is the key setting that makes this a passkey rather than a traditional FIDO2 credential. Resident keys (also called discoverable credentials) are stored on the authenticator indexed by rpId and userId. This allows authentication without supplying a credential ID first, which enables the passkey autofill experience.
userVerification: 'required' enforces that the authenticator performs local user verification: Face ID, Touch ID, Windows Hello, or a device PIN. Without this, any device possession is sufficient, which is weaker than intended.
attestation: 'none' is the right choice for consumer passkeys. Attestation proves the type and provenance of the authenticator hardware, which matters in enterprise FIDO2 deployments with hardware security policies. For passkeys, the complexity and cross-platform inconsistencies of attestation are not worth it.
Verifying Registration on the Server
The browser returns a PublicKeyCredential with an AuthenticatorAttestationResponse. The server must verify:
- The challenge matches and has not been used before.
- The
rpIdHashin the authenticator data matches yourrpId. - The
originin the client data matches your expected origins. - The
userPresentflag is set. - The
userVerifiedflag is set (since we required it). - The public key can be parsed and stored.
// lib/webauthn/verify-registration.ts
import { createHash } from 'crypto'
import * as cbor from 'cbor-x'
export interface VerifiedCredential {
credentialId: string // base64url
publicKey: string // COSE key, base64url encoded
counter: number
transports: string[]
aaguid: string
userVerified: boolean
}
export async function verifyRegistrationResponse(
response: {
clientDataJSON: string // base64url
attestationObject: string // base64url
transports: string[]
},
expectedChallenge: string,
expectedOrigins: string[]
): Promise<VerifiedCredential> {
// Decode and parse clientDataJSON
const clientDataBytes = Buffer.from(response.clientDataJSON, 'base64url')
const clientData = JSON.parse(clientDataBytes.toString('utf8'))
if (clientData.type !== 'webauthn.create') {
throw new Error('Invalid clientData type')
}
if (clientData.challenge !== expectedChallenge) {
throw new Error('Challenge mismatch')
}
if (!expectedOrigins.includes(clientData.origin)) {
throw new Error(`Unexpected origin: ${clientData.origin}`)
}
// Parse attestationObject
const attestationBuffer = Buffer.from(response.attestationObject, 'base64url')
const attestation = cbor.decode(attestationBuffer)
// Parse authenticator data
const authData = parseAuthenticatorData(attestation.authData)
// Verify rpIdHash
const expectedRpIdHash = createHash('sha256')
.update(process.env.RP_ID!)
.digest()
if (!authData.rpIdHash.equals(expectedRpIdHash)) {
throw new Error('rpId hash mismatch')
}
if (!authData.flags.userPresent) {
throw new Error('User presence flag not set')
}
if (!authData.flags.userVerified) {
throw new Error('User verification flag not set')
}
if (!authData.attestedCredentialData) {
throw new Error('No attested credential data in registration response')
}
return {
credentialId: authData.attestedCredentialData.credentialId.toString('base64url'),
publicKey: authData.attestedCredentialData.credentialPublicKey.toString('base64url'),
counter: authData.signCount,
transports: response.transports,
aaguid: formatAaguid(authData.attestedCredentialData.aaguid),
userVerified: authData.flags.userVerified,
}
}
function parseAuthenticatorData(authData: Buffer) {
let offset = 0
const rpIdHash = authData.subarray(offset, offset + 32)
offset += 32
const flagsByte = authData[offset]
offset += 1
const flags = {
userPresent: !!(flagsByte & 0x01),
userVerified: !!(flagsByte & 0x04),
attestedCredentialData: !!(flagsByte & 0x40),
extensionData: !!(flagsByte & 0x80),
}
const signCount = authData.readUInt32BE(offset)
offset += 4
let attestedCredentialData = null
if (flags.attestedCredentialData) {
const aaguid = authData.subarray(offset, offset + 16)
offset += 16
const credentialIdLength = authData.readUInt16BE(offset)
offset += 2
const credentialId = authData.subarray(offset, offset + credentialIdLength)
offset += credentialIdLength
const credentialPublicKey = authData.subarray(offset)
attestedCredentialData = { aaguid, credentialId, credentialPublicKey }
}
return { rpIdHash, flags, signCount, attestedCredentialData }
}
function formatAaguid(aaguid: Buffer): string {
const hex = aaguid.toString('hex')
return [
hex.slice(0, 8),
hex.slice(8, 12),
hex.slice(12, 16),
hex.slice(16, 20),
hex.slice(20),
].join('-')
}
In practice, use a well-maintained library like @simplewebauthn/server for production verification. The manual parsing above shows what the library does under the hood, which is worth understanding when debugging. The library handles COSE key decoding, signature verification, and attestation parsing across all algorithm variants.
The Authentication Ceremony
Authentication proves the user controls the private key corresponding to a stored public key. The server sends a challenge; the authenticator signs it; the server verifies the signature.
// lib/webauthn/authentication.ts
import { randomBytes } from 'crypto'
export interface AuthenticationChallenge {
challenge: string
rpId: string
allowCredentials: AllowCredential[]
userVerification: 'required' | 'preferred' | 'discouraged'
timeout: number
}
export interface AllowCredential {
id: string
type: 'public-key'
transports?: string[]
}
// For conditional UI (passkey autofill), pass empty allowCredentials
// For targeted authentication, pass the user's known credential IDs
export function generateAuthenticationOptions(
allowCredentials: AllowCredential[] = []
): AuthenticationChallenge {
return {
challenge: randomBytes(32).toString('base64url'),
rpId: process.env.RP_ID!,
allowCredentials,
userVerification: 'required',
timeout: 60000,
}
}
On the browser, call navigator.credentials.get():
// browser/passkey-auth.ts
export async function authenticateWithPasskey(
opts: AuthenticationChallenge
): Promise<PublicKeyCredential> {
const credential = await navigator.credentials.get({
publicKey: {
challenge: base64urlToBuffer(opts.challenge),
rpId: opts.rpId,
allowCredentials: opts.allowCredentials.map((c) => ({
id: base64urlToBuffer(c.id),
type: c.type,
transports: c.transports as AuthenticatorTransport[],
})),
userVerification: opts.userVerification,
timeout: opts.timeout,
},
}) as PublicKeyCredential
return credential
}
The verification on the server checks the signature against the stored public key and validates the counter. The counter is a monotonically increasing value the authenticator increments on each use. If the server receives a counter value lower than or equal to the stored value, it indicates a cloned authenticator (the private key was somehow extracted and copied). The correct response is to flag the credential and optionally revoke it.
// lib/webauthn/verify-authentication.ts
import { createVerify, createHash } from 'crypto'
import * as cbor from 'cbor-x'
export async function verifyAuthenticationResponse(
response: {
credentialId: string
clientDataJSON: string
authenticatorData: string
signature: string
userHandle: string | null
},
storedCredential: {
publicKey: string // COSE key, base64url
counter: number
},
expectedChallenge: string,
expectedOrigins: string[]
): Promise<{ newCounter: number; userVerified: boolean }> {
const clientDataBytes = Buffer.from(response.clientDataJSON, 'base64url')
const clientData = JSON.parse(clientDataBytes.toString('utf8'))
if (clientData.type !== 'webauthn.get') {
throw new Error('Invalid clientData type')
}
if (clientData.challenge !== expectedChallenge) {
throw new Error('Challenge mismatch')
}
if (!expectedOrigins.includes(clientData.origin)) {
throw new Error(`Unexpected origin: ${clientData.origin}`)
}
const authDataBuffer = Buffer.from(response.authenticatorData, 'base64url')
// Parse flags and counter from authenticator data
const flagsByte = authDataBuffer[32]
const userPresent = !!(flagsByte & 0x01)
const userVerified = !!(flagsByte & 0x04)
const newCounter = authDataBuffer.readUInt32BE(33)
if (!userPresent) throw new Error('User presence flag not set')
if (!userVerified) throw new Error('User verification flag not set')
// Clone detection
if (newCounter !== 0 && newCounter <= storedCredential.counter) {
throw new Error('Counter regression detected: possible credential clone')
}
// Build the signed data: authData || hash(clientDataJSON)
const clientDataHash = createHash('sha256').update(clientDataBytes).digest()
const signedData = Buffer.concat([authDataBuffer, clientDataHash])
// Decode COSE public key and verify signature
const publicKeyCose = cbor.decode(
Buffer.from(storedCredential.publicKey, 'base64url')
)
const verified = verifySignature(publicKeyCose, signedData, response.signature)
if (!verified) {
throw new Error('Signature verification failed')
}
return { newCounter, userVerified }
}
After a successful authentication, update the stored counter to newCounter. A counter of zero means the authenticator does not implement counters (common with synced passkeys, where a counter across multiple devices is problematic). That is acceptable: skip the clone detection check when newCounter === 0.
Credential Storage and Management
Passkeys require a credential table that relates credentials to users with enough metadata to support lifecycle management:
// db/schema.ts (Drizzle example)
import { pgTable, text, integer, timestamp, boolean } from 'drizzle-orm/pg-core'
export const passkeys = pgTable('passkeys', {
id: text('id').primaryKey(), // credential ID, base64url
userId: text('user_id').notNull(),
publicKey: text('public_key').notNull(), // COSE key, base64url
counter: integer('counter').notNull().default(0),
aaguid: text('aaguid'), // identifies the authenticator type
transports: text('transports').array(), // ['internal', 'hybrid', 'usb', 'nfc', 'ble']
deviceName: text('device_name'), // user-assigned label
backedUp: boolean('backed_up').notNull().default(false),
createdAt: timestamp('created_at').notNull().defaultNow(),
lastUsedAt: timestamp('last_used_at'),
})
The backedUp flag comes from the authenticator data flags (BE bit: backup eligibility; BS bit: currently backed up). A synced passkey on a phone will have backedUp: true because the credential syncs to iCloud or Google. A hardware security key will have backedUp: false. This distinction matters for your security model: backed-up credentials survive device loss, unsynced credentials do not.
Store the AAGUID. It identifies the authenticator type (Apple’s iCloud Keychain has a well-known AAGUID, as does Google Authenticator). The FIDO Alliance maintains a metadata service that maps AAGUIDs to authenticator names, which is useful for showing users “Passkey from Apple iCloud Keychain” instead of a raw credential ID.
Conditional UI: Passkey Autofill
The passkey autofill experience (the browser suggesting passkeys in the username field dropdown) requires conditional mediation. The key: call navigator.credentials.get() with mediation: 'conditional' early in the page lifecycle, before any user interaction.
// browser/conditional-ui.ts
export async function initConditionalPasskeyAuth(
onSuccess: (credential: PublicKeyCredential) => Promise<void>,
onError?: (error: unknown) => void
): Promise<void> {
// Check support before proceeding
if (
!window.PublicKeyCredential ||
!PublicKeyCredential.isConditionalMediationAvailable
) {
return
}
const supported = await PublicKeyCredential.isConditionalMediationAvailable()
if (!supported) return
// Fetch a challenge from the server
const challengeRes = await fetch('/api/auth/passkey/challenge')
const opts = await challengeRes.json()
try {
// This call does not show a dialog. It waits for the user to interact
// with the autofill UI. It resolves when the user selects a passkey.
const credential = await navigator.credentials.get({
publicKey: {
challenge: base64urlToBuffer(opts.challenge),
rpId: opts.rpId,
allowCredentials: [], // Empty: discovers all available passkeys for this RP
userVerification: 'required',
timeout: 300000, // 5 minutes, browser will manage the UI lifecycle
},
mediation: 'conditional', // The key property
}) as PublicKeyCredential
await onSuccess(credential)
} catch (error) {
// AbortError is expected if the user dismisses or navigates away
if ((error as DOMException).name !== 'AbortError') {
onError?.(error)
}
}
}
Add autocomplete="username webauthn" to the username input field. The browser will present available passkeys in the autocomplete dropdown. When the user selects one, the credentials.get() promise resolves with the selected credential.
This call must be initiated before the user focuses the username field. Put it in your page initialization code, not inside a click handler.
Cross-Device Authentication with Hybrid Transport
Hybrid transport (formerly known as caBLE: cloud-assisted Bluetooth Low Energy) allows authenticating with a passkey stored on a phone when the user is on a desktop that does not have that passkey. The flow: the desktop displays a QR code; the user scans it with their phone; the phone performs the biometric check and sends the signed assertion back to the desktop via an encrypted channel through the platform’s cloud service.
From the developer’s perspective, this requires no code changes. The browser handles the QR code display and the encrypted channel when hybrid is included in the credential’s transports array. The allowCredentials array, if populated, should include the known transports for each credential. If you store transports at registration time (which you should), pass them back at authentication time:
// Fetch user's credentials for targeted authentication
const credentials = await db
.select()
.from(passkeys)
.where(eq(passkeys.userId, userId))
const opts = generateAuthenticationOptions(
credentials.map((c) => ({
id: c.id,
type: 'public-key' as const,
transports: c.transports ?? undefined,
}))
)
When transports includes hybrid, the browser knows this credential might be on a nearby phone and offers the QR code flow. Without stored transports, the browser has to guess, which can result in a degraded UX where the wrong options appear.
Tradeoffs
| Concern | Passkeys (Synced) | Device-Bound FIDO2 | Hardware Security Key |
|---|---|---|---|
| Phishing resistance | Full | Full | Full |
| Device loss recovery | Automatic (syncs) | Manual re-enrollment | Manual re-enrollment |
| Clone resistance | Lower (intentionally synced) | High | High |
| Cross-device auth | Via hybrid transport | Via hybrid transport | USB/NFC/BLE direct |
| Enterprise attestation | Not available | Optional | Supported |
| User friction | Low (biometric or PIN) | Low | Moderate (plug in / tap) |
| Platform dependency | iCloud / Google / vendor PM | None | None |
| Credential backup | Yes (cloud sync) | No | No |
| Regulatory fit | Consumer apps | Enterprise with MFA policy | High-assurance contexts |
Migrating Password Users to Passkeys
Migration is where most implementations stall. You cannot force users onto passkeys instantly, and for a significant portion of your user base, passkeys will not be available (older browsers, managed corporate devices, Linux desktops without biometric sensors).
The practical strategy is additive enrollment with gradual promotion:
Step 1: Enroll during active sessions. After a successful password login, prompt the user to add a passkey. Do not force it. Track enrollment rates per cohort to measure uptake.
// After successful password auth, check if passkey enrollment should be prompted
export async function shouldPromptPasskeyEnrollment(
userId: string,
db: Database
): Promise<boolean> {
const existingPasskeys = await db
.select({ id: passkeys.id })
.from(passkeys)
.where(eq(passkeys.userId, userId))
.limit(1)
if (existingPasskeys.length > 0) return false // Already enrolled
const user = await db.select().from(users).where(eq(users.id, userId)).get()
if (!user) return false
// Only prompt accounts older than 7 days to avoid prompting brand-new users
const ageMs = Date.now() - new Date(user.createdAt).getTime()
return ageMs > 7 * 24 * 60 * 60 * 1000
}
Step 2: Conditional UI on login page. Once some users have passkeys enrolled, the login page should initialize conditional mediation so enrolled users are prompted automatically. Non-enrolled users see the normal password form.
Step 3: Deprecation signaling. Once passkey enrollment is above a threshold (say, 60% of active users), add soft signals: a banner noting passwords are deprecated, a nudge in account settings. Still do not remove password auth.
Step 4: Password auth removal. Only appropriate for new sign-ups or when you have a verified passkey for every account. For existing accounts, keep password as a fallback unless you have an account recovery flow that does not depend on email (email itself can be phished).
Fallback Strategies
Passkey authentication will fail for some users on some devices. You need fallbacks that do not undermine the security model:
Email magic link: A time-limited, single-use token sent to the verified email address. Secure enough for most applications, phishable (the email can be intercepted), but the bar is much higher than a password.
SMS OTP: Higher friction, lower security than email magic link for most threat models (SIM swapping is a real attack vector). Acceptable for consumer apps with account recovery use cases.
Recovery codes: Generated at passkey enrollment time, shown once, stored by the user. High security, high friction. Appropriate for high-value accounts where the user would be motivated to keep them.
Admin recovery: For B2B applications where an org admin can verify identity out-of-band and reset access. Avoids the complexity of building cryptographically strong recovery into the self-serve flow.
Do not quietly fall back to password auth for users who have passkeys enrolled. That fallback eliminates the phishing resistance you added. If a user claims they cannot use their passkey, require explicit identity verification before allowing a password reset.
Production Considerations
Multiple origins in development. Your RP ID is a domain (yourapp.com), and all subdomains can use it (app.yourapp.com, admin.yourapp.com). In local development, localhost is a special case that does not require HTTPS. But when testing passkey features across environments, the challenge server must return rpId matching the domain the browser loaded the page from, or verification will fail with a cryptic error.
// Determine rpId based on environment
export function getRpId(): string {
if (process.env.NODE_ENV === 'development') {
return 'localhost'
}
return process.env.RP_ID! // e.g. "yourapp.com"
}
export function getExpectedOrigins(): string[] {
if (process.env.NODE_ENV === 'development') {
return ['http://localhost:3000']
}
return [
`https://${process.env.RP_ID}`,
`https://www.${process.env.RP_ID}`,
]
}
Challenge expiry and one-time use. Store pending challenges in Redis with a short TTL (120 seconds is plenty). After a challenge is consumed (successfully or not), delete it immediately. A challenge that can be used twice enables replay attacks within the validity window.
// lib/webauthn/challenge-store.ts
import { createClient } from 'redis'
const redis = createClient({ url: process.env.REDIS_URL })
export async function storeChallenge(
sessionId: string,
challenge: string
): Promise<void> {
await redis.setEx(`webauthn:challenge:${sessionId}`, 120, challenge)
}
export async function consumeChallenge(
sessionId: string
): Promise<string | null> {
const challenge = await redis.getDel(`webauthn:challenge:${sessionId}`)
return challenge
}
Credential ID collisions. Credential IDs are generated by the authenticator, not your server. They are opaque byte strings, typically 16-32 bytes. Treat them as unique across your entire credential table, not per-user. The WebAuthn spec does not guarantee uniqueness across users, but in practice collisions are cryptographically implausible. Index the id column as a primary key.
Credential metadata for user management. Users need to see and manage their passkeys. Expose a list that shows device name (derived from AAGUID lookup or user-provided label), when it was added, and when it was last used. Allow deletion. When a user deletes a passkey and has no others, require them to set an alternative credential before completing the deletion.
Counter update must be atomic. After a successful authentication, update the counter in the same database transaction as creating the session. If the counter update fails and the old counter is still stored, a reuse attack becomes possible within the window where the counter is stale.
// Wrap session creation and counter update in a transaction
await db.transaction(async (tx) => {
await tx
.update(passkeys)
.set({ counter: newCounter, lastUsedAt: new Date() })
.where(eq(passkeys.id, credentialId))
await createSession(tx, { userId, email, authMethod: 'passkey' })
})
Step-up authentication. For sensitive operations (changing email, deleting an account, accessing billing), require passkey re-authentication even for an active session. The WebAuthn userVerification: 'required' flag ensures the authenticator confirms user presence again, providing a fresh biometric assertion that is both phishing-resistant and device-bound.
The move from passwords to passkeys is not a flag you flip. It is a migration with distinct phases: enrollment, parallel support, promotion, and eventual deprecation. The protocol mechanics are well-specified, but the lifecycle challenges, covering device loss, cross-device enrollment, fallback paths, and credential management UI, are where implementations fail. Build for the full lifecycle from the start.
More in Web Engineering
How Rspack Works Internally: Webpack-Compatible Module Graph, Rust Compilation Pipeline, and the Incremental Build Architecture Behind the Fastest Webpack Replacement
A deep dive into Rspack's Rust-based architecture, module graph construction, SWC transformation pipeline, webpack compatibility layer, incremental compilation, and what the tradeoffs look like for teams migrating from webpack.
How Turbopack Works Internally: Incremental Computation, Function-Level Caching, and the Rust-Based Bundler Architecture Behind Next.js
A deep dive into Turbopack's architecture covering the Turbo engine's incremental computation model, function-level caching, Rust-based module resolution, granular HMR invalidation, SWC integration, persistent caching, and how it compares to webpack, Vite, esbuild, and Rspack.
How gRPC Works: Protocol Buffers, HTTP/2 Multiplexing, and Bidirectional Streaming From Service Definition to Wire Format
A deep dive into gRPC internals: Protocol Buffer IDL and the buf codegen pipeline, HTTP/2 stream multiplexing and HPACK header compression, all four RPC patterns with TypeScript, channel management, deadline propagation, interceptors, load balancing from pick-first to xDS, the health checking protocol, and production tradeoffs vs REST and GraphQL.
How Node.js Works Internally: The Event Loop, libuv Thread Pool, and Async I/O Architecture From require() to Process Exit
A deep dive into Node.js internals covering V8 JIT compilation, the six-phase libuv event loop, thread pool mechanics, microtask queue priority, async/await desugaring, CommonJS versus ESM module resolution, and production tuning considerations with TypeScript examples.