Lesson 10-Nuxt.js Advanced Features

Incremental Static Regeneration (ISR)

Core Concepts and Configuration of ISR

ISR Core Mechanism:

// packages/nuxt/src/core/runtime/nitro/renderer.ts
export class ISRRenderer {
  private revalidateMap: Map<string, number> = new Map()

  // Get page revalidate configuration
  getRevalidateConfig(routePath: string): number | false {
    const pageConfig = this.getPageConfig(routePath)
    return pageConfig?.revalidate ?? this.globalRevalidate
  }

  // Execute ISR
  async handleISR(routePath: string, renderFn: () => Promise<string>) {
    const revalidate = this.getRevalidateConfig(routePath)

    if (revalidate === false) {
      // Pure SSR mode
      return renderFn()
    }

    // Check cache
    const cached = await this.getCache(routePath)
    if (cached && !this.isCacheExpired(cached, revalidate)) {
      return cached.html
    }

    // Perform re-rendering
    const html = await renderFn()

    // Update cache
    await this.setCache(routePath, {
      html,
      timestamp: Date.now()
    })

    return html
  }
}

revalidate Parameter Configuration Example:

// nuxt.config.js
export default {
  nitro: {
    isr: {
      // Global default revalidate time (seconds)
      revalidate: 60
    }
  },
  pages: {
    '/blog/:id': {
      // Specific configuration for dynamic routes
      revalidate: 30 // 30 seconds revalidation
    },
    '/about': {
      revalidate: false // Disable ISR, pure SSR mode
    }
  }
}

ISR Implementation for Dynamic Routes

Dynamic Route Parameter Handling:

// packages/nuxt/src/core/runtime/nitro/route.ts
export class DynamicRouteHandler {
  // Parse dynamic route parameters
  parseDynamicParams(routePattern: string, requestPath: string) {
    const paramNames = this.extractParamNames(routePattern)
    const regex = this.convertToRegex(routePattern)
    const match = requestPath.match(regex)

    if (!match) throw new Error('Route not matched')

    return paramNames.reduce((params, name, index) => {
      params[name] = decodeURIComponent(match[index + 1])
      return params
    }, {} as Record<string, string>)
  }

  // Generate cache key for dynamic routes
  generateCacheKey(routePath: string, params: Record<string, string>) {
    const sortedParams = Object.keys(params)
      .sort()
      .map(key => `${key}=${params[key]}`)
      .join('&')

    return `${routePath}?${sortedParams}`
  }
}

asyncData and revalidate Integration:

// packages/nuxt/src/core/runtime/nitro/data-fetching.ts
export async function handleAsyncData(
  context: NuxtContext,
  routePath: string,
  asyncDataFn?: () => Promise<any>
) {
  // 1. Check ISR configuration
  const revalidate = context.nitro.isr.getRevalidateConfig(routePath)

  // 2. Execute data fetching
  let data = {}
  if (asyncDataFn) {
    data = await asyncDataFn(context)
  }

  // 3. ISR handling
  if (revalidate !== false) {
    const cacheKey = context.nitro.isr.generateCacheKey(routePath, context.params)
    await context.nitro.isr.handleISR(cacheKey, async () => {
      // Re-execute asyncData
      return asyncDataFn ? await asyncDataFn(context) : {}
    })
  }

  return data
}

Performance Optimization and Cache Strategies for ISR

Multi-Level Cache Implementation:

// packages/nuxt/src/core/runtime/nitro/cache.ts
export class MultiLevelCache {
  // Memory cache (fast access)
  private memoryCache = new Map<string, CacheItem>()

  // Persistent cache (disk/database)
  private persistentCache: PersistentCache

  async get(key: string): Promise<CacheItem | null> {
    // 1. Check memory cache
    if (this.memoryCache.has(key)) {
      const item = this.memoryCache.get(key)!
      if (!this.isExpired(item)) {
        return item
      }
      this.memoryCache.delete(key)
    }

    // 2. Check persistent cache
    const persistentItem = await this.persistentCache.get(key)
    if (persistentItem && !this.isExpired(persistentItem)) {
      // Backfill memory cache
      this.memoryCache.set(key, persistentItem)
      return persistentItem
    }

    return null
  }

  async set(key: string, value: CacheItem) {
    // Set memory cache (with TTL)
    this.memoryCache.set(key, value)
    setTimeout(() => this.memoryCache.delete(key), value.ttl)

    // Asynchronously write to persistent cache
    this.persistentCache.set(key, value)
  }
}

Cache Warming Strategy:

// packages/nuxt/src/core/runtime/nitro/warmup.ts
export class CacheWarmer {
  async warmupRoutes(routes: string[]) {
    // Warm up all routes in parallel
    await Promise.all(
      routes.map(async route => {
        // 1. Get route configuration
        const config = this.getRouteConfig(route)

        // 2. Perform rendering
        const html = await this.renderRoute(route, config)

        // 3. Populate cache
        await this.cache.set(
          route,
          {
            html,
            timestamp: Date.now(),
            ttl: config.revalidate * 1000
          }
        )
      })
    )
  }
}

Comparison and Selection Between ISR and SSR

Decision Matrix:

FeatureISRSSR
First Load PerformanceSlower (initial generation required)Fast (direct generation)
Subsequent Request PerformanceVery Fast (cache hit)Moderate (generated each time)
Server LoadLow (no processing on cache hit)High (processed every request)
Data FreshnessConfigurable (via revalidate)Real-time (latest data per request)
Use CasesRelatively stable contentHighly dynamic content
Implementation ComplexityModerate (requires cache logic)Simple (direct rendering)

Selection Guidelines:

  • Choose ISR when:
    • Content updates infrequently (e.g., blog posts)
    • Brief data delays are acceptable (configured via revalidate)
    • Server load reduction is needed
  • Choose SSR when:
    • Content is highly dynamic (e.g., user dashboards)
    • Real-time data is required
    • Higher server load is acceptable

Security and Access Control for ISR

Permission-Based Cache Strategy:

// packages/nuxt/src/core/runtime/nitro/auth.ts
export class AuthAwareCache {
  async getWithAuth(key: string, getUser: () => Promise<User | null>) {
    const user = await getUser()
    const cacheKey = this.getUserSpecificKey(key, user)

    // Get cache
    const cached = await this.cache.get(cacheKey)
    if (cached && !this.isExpired(cached)) {
      return cached.html
    }

    // Perform re-rendering
    const html = await this.renderWithAuth(key, user)

    // Update cache
    await this.cache.set(cacheKey, {
      html,
      timestamp: Date.now(),
      ttl: this.getTTLForUser(user)
    })

    return html
  }

  private getUserSpecificKey(baseKey: string, user: User | null) {
    if (!user) return `anon:${baseKey}`
    return `user:${user.id}:${baseKey}`
  }

  private getTTLForUser(user: User | null): number {
    if (!user) return 60 // Short TTL for anonymous users
    if (user.role === 'admin') return 0 // Disable cache for admins
    return 300 // 5-minute TTL for regular users
  }
}

Sensitive Data Protection:

// packages/nuxt/src/core/runtime/nitro/sensitive-data.ts
export function protectSensitiveData(data: any, context: NuxtContext) {
  // 1. Check if data contains sensitive fields
  if (this.hasSensitiveFields(data)) {
    // 2. Check user permissions
    if (!this.hasPermission(context.user, 'view_sensitive_data')) {
      // 3. Filter sensitive data
      return this.filterSensitiveFields(data)
    }
  }

  return data
}

// Integrate with ISR process
export async function handleISRSensitiveRoute(
  routePath: string,
  renderFn: () => Promise<string>,
  context: NuxtContext
) {
  // 1. Perform rendering (server-side)
  const html = await renderFn()

  // 2. Filter sensitive data during client-side hydration
  if (context.isClient) {
    return this.protectSensitiveData(html, context)
  }

  // 3. Fully filter during server-side caching
  return this.protectSensitiveData(html, context)
}

Advanced Module System

Nuxt Module Development and Integration

Module Development Template:

// Example custom module
export default defineNuxtModule({
  meta: {
    name: 'my-module',
    configKey: 'myModule'
  },
  // Default configuration
  defaults: {
    featureFlag: true,
    apiEndpoint: '/api'
  },
  // Hook functions
  hooks: {
    'app:created'(nuxtApp) {
      // Execute when app is created
    },
    'pages:extend'(pages) {
      // Extend page routes
    }
  },
  // Runtime configuration
  runtimeConfig: {
    public: {
      // Client-accessible configuration
      featureFlag: true
    }
  },
  // Setup method
  setup(options, nuxt) {
    // 1. Add runtime configuration
    nuxt.options.runtimeConfig.myModule = options

    // 2. Extend Vue prototype
    nuxt.hook('vue:setup', (app) => {
      app.config.globalProperties.$myModule = {
        getData() { /* ... */ }
      }
    })

    // 3. Register plugin
    nuxt.hook('nitro:config', (config) => {
      config.plugins.push('./runtime/plugin')
    })
  }
})

Module Integration Process:

// packages/nuxt/src/core/modules/index.ts
export async function loadModules(nuxt: Nuxt) {
  // 1. Load built-in modules
  await loadBuiltInModules(nuxt)

  // 2. Load project modules
  const modulePaths = await findModulePaths(nuxt.options.rootDir)
  for (const path of modulePaths) {
    await loadModule(path, nuxt)
  }

  // 3. Apply module configurations
  applyModuleConfigs(nuxt)

  // 4. Execute module hooks
  await runModuleHooks('init', nuxt)
}

Module Configuration and Lifecycle

Module Configuration Merge Strategy:

// packages/nuxt/src/core/config/modules.ts
export function mergeModuleConfigs(
  defaultConfig: Record<string, any>,
  userConfig: Record<string, any>
) {
  // 1. Deep merge configurations
  const merged = deepMerge(defaultConfig, userConfig)

  // 2. Handle environment variable overrides
  if (process.env.NUXT_MODULE_OPTIONS) {
    const envOverrides = JSON.parse(process.env.NUXT_MODULE_OPTIONS)
    return deepMerge(merged, envOverrides)
  }

  return merged
}

// Module lifecycle management
export class ModuleManager {
  private modules: Map<string, NuxtModule> = new Map()

  async register(module: NuxtModule) {
    // 1. Initialize module
    await module.setup()

    // 2. Register hooks
    this.registerHooks(module)

    // 3. Store module reference
    this.modules.set(module.meta.name, module)
  }

  async runHook(hookName: string, ...args: any[]) {
    // Execute all module hooks in order
    for (const module of this.modules.values()) {
      if (module.hooks[hookName]) {
        await module.hooks[hookName](...args)
      }
    }
  }
}

Differences and Relationships Between Modules and Plugins

Modules vs. Plugins Comparison Table:

FeatureModule (Nuxt Module)Plugin (Plugin)
PurposeExtend Nuxt core functionalityExtend Vue app functionality
Loading TimeDuring Nuxt initializationDuring Vue app creation
Configuration CapabilitySupports full configuration systemTypically simple configuration
Integration DepthAccess to Nuxt internal APIsMainly access to Vue context
Typical Use CasesAdd server middleware, build toolsAdd Vue directives, global methods
Dependency ManagementAutomatically handles module dependenciesRequires manual dependency management

Plugin Integration Example:

// Register plugin in a module
export default defineNuxtModule({
  setup(options, nuxt) {
    nuxt.hook('vue:setup', (app) => {
      // Register Vue plugin
      app.use(MyVuePlugin, { /* Plugin options */ })
    })

    // Register Nitro plugin (server-side)
    nuxt.hook('nitro:config', (config) => {
      config.plugins.push('./server-plugin')
    })
  }
})

Module Security and Access Control

Module Sandbox Mechanism:

// packages/nuxt/src/core/security/module-sandbox.ts
export class ModuleSandbox {
  private allowedAPIs: Set<string> = new Set([
    'console.log',
    'setTimeout',
    // ...other safe APIs
  ])

  execute(code: string, context: Record<string, any>) {
    // 1. Create proxy environment
    const proxy = this.createProxy(context)

    // 2. Safely execute
    try {
      // Use Function constructor instead of eval
      const fn = new Function(...Object.keys(proxy), code)
      return fn(...Object.values(proxy))
    } catch (error) {
      this.handleSecurityError(error)
    }
  }

  private createProxy(context: Record<string, any>) {
    return new Proxy(context, {
      get(target, prop) {
        const fullPath = `globalThis.${prop}`
        if (this.allowedAPIs.has(fullPath)) {
          return target[prop]
        }
        throw new Error(`Access to ${prop} is restricted`)
      }
    })
  }
}

Module Permission Verification:

// packages/nuxt/src/core/security/module-auth.ts
export class ModuleAuth {
  async verifyModuleAccess(moduleName: string, user: User | null) {
    // 1. Get module metadata
    const meta = this.getModuleMeta(moduleName)

    // 2. Check permission requirements
    if (meta.requiredRole) {
      if (!user || !this.hasRole(user, meta.requiredRole)) {
        throw new Error(`Access to ${moduleName} requires ${meta.requiredRole} role`)
      }
    }

    // 3. Check feature flags
    if (meta.featureFlag && !this.isFeatureEnabled(meta.featureFlag)) {
      throw new Error(`Feature ${meta.featureFlag} is disabled`)
      }

    return true
  }
}

Module Performance Optimization

Module Lazy Loading Implementation:

// packages/nuxt/src/core/modules/lazy.ts
export class LazyModuleLoader {
  private loadedModules = new Set<string>()

  async load(moduleName: string) {
    // 1. Check if already loaded
    if (this.loadedModules.has(moduleName)) {
      return
    }

    // 2. Dynamically import module
    try {
      const module = await import(`../modules/${moduleName}`)

      // 3. Initialize module
      await module.default.setup()

      // 4. Mark as loaded
      this.loadedModules.add(moduleName)
    } catch (error) {
      console.error(`Failed to load module ${moduleName}:`, error)
    }
  }

  // Route-based module preloading
  setupRouteBasedPreloading(nuxt: Nuxt) {
    nuxt.hook('pages:extend', (pages) => {
      pages.forEach(page => {
        page.middleware.push(async (context) => {
          // Preload modules potentially needed for this route
          const requiredModules = this.detectRequiredModules(page.path)
          await Promise.all(requiredModules.map(m => this.load(m)))
        })
      })
    })
  }
}

Module Bundling Optimization:

// packages/nuxt/src/build/modules-optimizer.ts
export class ModuleOptimizer {
  optimize(modules: NuxtModule[]) {
    // 1. Analyze module dependency graph
    const dependencyGraph = this.buildDependencyGraph(modules)

    // 2. Identify parallel-loadable modules
    const parallelGroups = this.identifyParallelGroups(dependencyGraph)

    // 3. Generate optimized loading order
    const optimizedOrder = this.generateOptimizedOrder(parallelGroups)

    // 4. Apply code splitting
    this.applyCodeSplitting(optimizedOrder)

    return {
      loadingOrder: optimizedOrder,
      codeSplitConfig: this.getCodeSplitConfig()
    }
  }

  private buildDependencyGraph(modules: NuxtModule[]) {
    // Build module dependency graph
    // ...
  }
}

Image and Font Optimization

Advanced Configuration of Nuxt Image Module

Advanced Image Configuration Example:

// nuxt.config.js
export default {
  image: {
    // Custom loader
    domains: ['images.example.com'],
    provider: 'cloudinary',
    providerOptions: {
      cloudName: 'my-cloud',
      apiKey: '12345'
    },

    // Image quality settings
    quality: 80,
    formats: ['webp', 'jpeg'],

    // Responsive breakpoints
    screens: {
      xs: 320,
      sm: 640,
      md: 1024,
      lg: 1280,
      xl: 1920
    },

    // Blur placeholder
    presets: {
      avatar: {
        modifiers: {
          format: 'png',
          width: 50,
          height: 50,
          quality: 50
        }
      }
    }
  }
}

Dynamic Image Processing Source Code:

// packages/nuxt/image/src/runtime/component.ts
export default defineComponent({
  props: {
    src: { type: String, required: true },
    width: { type: [Number, String], default: null },
    height: { type: [Number, String], default: null },
    quality: { type: Number, default: null },
    format: { type: String, default: null }
  },
  async setup(props) {
    // 1. Generate optimized image URL
    const optimizedUrl = await generateOptimizedUrl({
      src: props.src,
      width: props.width,
      height: props.height,
      quality: props.quality || nuxtImageConfig.quality,
      format: props.format || nuxtImageConfig.formats[0]
    })

    // 2. Load blur placeholder
    const placeholderUrl = await generatePlaceholderUrl(props.src)

    return {
      optimizedUrl,
      placeholderUrl
    }
  }
})

Font Optimization Strategies

Google Fonts Optimization Integration:

// nuxt.config.js
export default {
  googleFonts: {
    families: {
      Roboto: [400, 700],
      'Open+Sans': true
    },
    display: 'swap',
    preload: true,
    inject: true
  }
}

Custom Font Loading Optimization:

// packages/nuxt/src/core/features/fonts.ts
export class FontOptimizer {
  async loadFonts() {
    // 1. Detect system fonts
    const systemFonts = this.detectSystemFonts()

    // 2. Load custom fonts (with preloading)
    if (this.hasCustomFonts()) {
      await this.preloadCriticalFonts()
      this.loadNonCriticalFonts()
    }

    // 3. Apply font display strategy
    this.setFontDisplayStrategy('swap')
  }

  private preloadCriticalFonts() {
    // Use <link rel="preload"> to preload critical fonts
    const criticalFonts = this.getCriticalFonts()
    criticalFonts.forEach(font => {
      const link = document.createElement('link')
      link.rel = 'preload'
      link.href = font.url
      link.as = 'font'
      link.type = 'font/woff2'
      link.crossOrigin = 'anonymous'
      document.head.appendChild(link)
    })
  }
}

Performance Optimization for Fonts and Images

Comprehensive Performance Optimization Strategies:

// packages/nuxt/src/core/performance/optimization.ts
export class AssetOptimizer {
  // Image optimization
  optimizeImages() {
    // 1. Generate responsive images
    this.generateResponsiveImages()

    // 2. Lazy load off-screen images
    this.lazyLoadOffscreenImages()

    // 3. Enable progressive image loading
    this.enableProgressiveLoading()
  }

  // Font optimization
  optimizeFonts() {
    // 1. Font subsetting
    this.subsetFontCharacters()

    // 2. Font display strategy
    this.setFontDisplayStrategy('swap')

    // 3. Preload critical fonts
    this.preloadCriticalFonts()
  }

  // Comprehensive optimization
  runFullOptimization() {
    // 1. Critical rendering path optimization
    this.optimizeCriticalAssets()

    // 2. Resource priority adjustment
    this.adjustResourcePriority()

    // 3. Configure cache policies
    this.configureCachePolicies()
  }
}

Cache Strategies for Fonts and Images

Smart Cache Implementation:

// packages/nuxt/src/core/cache/assets.ts
export class AssetCache {
  // Memory cache (fast access)
  private memoryCache = new Map<string, CachedAsset>()

  // Service worker cache
  private serviceWorkerCache: Cache

  async get(url: string): Promise<CachedAsset | null> {
    // 1. Check memory cache
    if (this.memoryCache.has(url)) {
      const cached = this.memoryCache.get(url)!
      if (!this.isExpired(cached)) {
        return cached
      }
      this.memoryCache.delete(url)
    }

    // 2. Check service worker cache
    if (this.serviceWorkerCache) {
      const response = await this.serviceWorkerCache.match(url)
      if (response) {
        const cached = await this.parseCachedResponse(response)
        this.memoryCache.set(url, cached) // Backfill memory cache
        return cached
      }
    }

    return null
  }

  async set(url: string, asset: CachedAsset) {
    // 1. Set memory cache
    this.memoryCache.set(url, asset)
    setTimeout(() => this.memoryCache.delete(url), asset.ttl)

    // 2. Update service worker cache
    if (this.serviceWorkerCache) {
      const response = await this.createCacheResponse(asset)
      await this.serviceWorkerCache.put(url, response)
    }
  }
}

Cache Invalidation Strategy:

// packages/nuxt/src/core/cache/invalidation.ts
export class CacheInvalidator {
  // Content hash-based cache invalidation
  async invalidateByContentHash() {
    // 1. Calculate current content hash
    const currentHash = await this.calculateContentHash()

    // 2. Compare with last cached hash
    const lastHash = await this.getLastCacheHash()
    if (currentHash !== lastHash) {
      // 3. Clear old cache
      await this.clearCache()

      // 4. Store new hash
      await this.storeCacheHash(currentHash)
    }
  }

  // Time-based cache invalidation
  async invalidateByTime(ttl: number) {
    const lastUpdated = await this.getLastUpdatedTime()
    if (Date.now() - lastUpdated > ttl * 1000) {
      await this.clearCache()
      await this.updateLastUpdatedTime()
    }
  }
}

Security and Compatibility for Fonts and Images

Secure Loading Strategy:

// packages/nuxt/src/core/security/assets.ts
export class AssetSecurity {
  // Secure image loading
  async loadSecureImage(url: string) {
    // 1. Validate URL source
    if (!this.isAllowedDomain(url)) {
      throw new Error('Image domain not allowed')
    }

    // 2. Get content security policy
    const csp = this.getContentSecurityPolicy()

    // 3. Securely load image
    return new Promise((resolve, reject) => {
      const img = new Image()
      img.crossOrigin = 'anonymous'
      img.src = url

      img.onload = () => resolve(img)
      img.onerror = () => reject(new Error('Failed to load image'))

      // Apply CSP restrictions
      if (csp) {
        this.applyCSPToImage(img, csp)
      }
    })
  }

  // Secure font loading
  async loadSecureFont(url: string) {
    // 1. Validate font source
    if (!this.isAllowedFontDomain(url)) {
      throw new Error('Font domain not allowed')
    }

    // 2. Check font MIME type
    const mimeType = await this.detectMimeType(url)
    if (!this.isAllowedFontType(mimeType)) {
      throw new Error('Font type not allowed')
    }

    // 3. Securely load font
    return this.loadFontWithCSP(url)
  }
}

Compatibility Handling:

// packages/nuxt/src/core/compatibility/assets.ts
export class AssetCompatibility {
  // Image format compatibility handling
  async getCompatibleImageFormat(preferredFormats: string[]) {
    // 1. Detect browser-supported formats
    const supportedFormats = await this.detectSupportedFormats()

    // 2. Find best matching format
    for (const format of preferredFormats) {
      if (supportedFormats.includes(format)) {
        return format
      }
    }

    // 3. Fallback to default format
    return 'jpeg'
  }

  // Font loading compatibility handling
  async loadCompatibleFont(fontConfig: FontConfig) {
    // 1. Detect browser font support
    const fontSupport = await this.detectFontSupport()

    // 2. Adjust font configuration
    const adjustedConfig = this.adjustFontConfig(fontConfig, fontSupport)

    // 3. Load adjusted font
    return this.loadFont(adjustedConfig)
  }
}
Share your love