Lesson 12-Nuxt.js Advanced Application Development

Server-Side Rendering (SSR)

Advanced Use of asyncData

Advanced asyncData Pattern:

// pages/products/_id.vue
export default defineComponent({
  async asyncData({ params, $axios, error, req }) {
    try {
      // 1. Parallel requests to multiple data sources
      const [product, related] = await Promise.all([
        $axios.$get(`/api/products/${params.id}`),
        $axios.$get(`/api/products/${params.id}/related`)
      ])

      // 2. Use request context
      const userAgent = req?.headers['user-agent'] || ''
      const isMobile = /mobile/i.test(userAgent)

      // 3. Data preprocessing
      const enrichedProduct = {
        ...product,
        isFavorite: checkUserFavorite(product.id), // Client state check
        meta: generateMetaTags(product, isMobile)
      }

      return {
        product: enrichedProduct,
        relatedProducts: related
      }
    } catch (err) {
      // 4. Error handling
      error({
        statusCode: err.response?.status || 500,
        message: err.message
      })
    }
  }
})

SSR Data Prefetching Strategy:

// composables/useProductData.ts
export const useProductData = (id: string) => {
  // 1. Server-first fetching
  const { data, error } = await useAsyncData(
    `product-${id}`,
    () => $fetch(`/api/products/${id}`),
    {
      server: true, // Force server-side fetching
      lazy: false   // Immediate execution
    }
  )

  // 2. Client-side supplemental data
  const clientSideData = ref(null)
  if (process.client) {
    onMounted(async () => {
      clientSideData.value = await $fetch(`/api/products/${id}/client-data`)
    })
  }

  return {
    product: computed(() => data.value || clientSideData.value),
    error
  }
}

SSR Performance Optimization and Cache Strategies

Multi-Level Cache Implementation:

// server/middleware/cache.ts
export default defineEventHandler(async (event) => {
  const cache = useCache()
  const key = getRequestCacheKey(event)

  // 1. Check memory cache
  if (cache.has(key)) {
    return cache.get(key)
  }

  // 2. Check Redis cache
  const redisData = await redis.get(key)
  if (redisData) {
    const data = JSON.parse(redisData)
    cache.set(key, data) // Backfill memory cache
    return data
  }

  // 3. Execute actual request
  const response = await handleRequest(event)

  // 4. Set cache
  const ttl = getCacheTTL(event) // Dynamically calculate TTL
  cache.set(key, response, ttl)
  await redis.set(key, JSON.stringify(response), 'EX', ttl)

  return response
})

Component-Level Caching:

<!-- components/ProductList.vue -->
<script setup>
// Use <nuxt:cache> directive for component caching
definePageMeta({
  cache: {
    key: ({ params }) => `products-${params.category}`,
    maxAge: 60 * 5 // 5-minute cache
  }
})
</script>

<template>
  <nuxt:cache>
    <div v-for="product in products" :key="product.id">
      <!-- Product list content -->
    </div>
  </nuxt:cache>
</template>

SSR Security and Access Control

Role-Based Access Control:

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useUserStore()
  const requiredRoles = to.meta.requiredRoles || []

  if (requiredRoles.length > 0 && !requiredRoles.some(role => user.roles.includes(role))) {
    return navigateTo('/unauthorized')
  }

  // Sensitive data filtering
  if (to.meta.sensitiveData) {
    to.meta.dataFilter = (data) => {
      return filterSensitiveFields(data, user.permissions)
    }
  }
})

Request Validation Middleware:

// server/middleware/validation.ts
export default defineEventHandler(async (event) => {
  // 1. CSRF validation
  if (isUnsafeMethod(event.req.method)) {
    verifyCSRFToken(event)
  }

  // 2. Rate limiting
  const ip = event.node.req.headers['x-forwarded-for'] || event.node.req.socket.remoteAddress
  await rateLimit(ip)

  // 3. Request body validation
  if (event.req.method === 'POST') {
    const schema = getValidationSchema(event.node.req.url)
    if (schema) {
      await schema.validate(await readBody(event))
    }
  }
})

SSR Use Cases

Dynamic Content Rendering:

<!-- pages/news/_id.vue -->
<script setup>
const { data } = await useAsyncData('news-item', () => {
  return $fetch(`/api/news/${route.params.id}`, {
    headers: {
      'X-Request-Freshness': 'always' // Force fetch latest data
    }
  })
})
</script>

<template>
  <article v-if="data">
    <h1>{{ data.title }}</h1>
    <div v-html="data.content"></div>
    <!-- Dynamic ad slot -->
    <AdSlot :position="'news-'+data.id" />
  </article>
</template>

User Personalized Experience:

// composables/usePersonalization.ts
export const usePersonalization = () => {
  const user = useUserStore()

  // Server-side personalized data injection
  const personalizedData = ref(null)
  if (process.server) {
    personalizedData.value = {
      recommendations: getRecommendations(user.id),
      theme: getUserThemePreference(user.id)
    }
  }

  // Client-side supplemental personalization
  if (process.client) {
    onMounted(async () => {
      if (!personalizedData.value) {
        personalizedData.value = await fetchPersonalization()
      }
    })
  }

  return { personalizedData }
}

Combining SSR and ISR

Hybrid Rendering Strategy:

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    isr: {
      // Default ISR configuration
      revalidate: 60
    }
  },
  routeRules: {
    // Specific route overrides global config
    '/products/:id': {
      ssr: true,          // Enable SSR
      isr: {              // Override ISR settings
        revalidate: 30,   // 30-second revalidation
        swr: 300          // 5-minute stale-while-revalidate
      }
    },
    '/blog': {
      ssr: false,         // Disable SSR
      isr: {              // Pure static generation + ISR
        revalidate: 3600  // 1-hour revalidation
      }
    }
  }
})

Dynamic Route Hybrid Mode:

// server/routes.js
export default defineRoutes({
  // Dynamic route SSR handling
  '/user/:id': {
    handler: async (req, res) => {
      const user = await getUserFromDB(req.params.id)
      if (!user) throw createError(404)

      // Critical user data SSR rendering
      const html = await renderSSR('user-profile', { user })

      // Non-critical data fetched client-side
      const script = `
        <script>
          window.__USER_DATA__ = ${JSON.stringify(user)}
          fetch('/api/user/${req.params.id}/preferences')
            .then(res => res.json())
            .then(data => {
              window.__USER_PREFS__ = data
            })
        </script>
      `

      res.send(html + script)
    },
    // ISR fallback
    isr: {
      revalidate: 60
    }
  }
})

Static Site Generation (SSG)

Advanced Use of asyncData and fetch

Prerendering Data Strategy:

// pages/docs/_slug.vue
export default defineComponent({
  async asyncData({ params, $content }) {
    // 1. Prefetch content during static generation
    const doc = await $content('docs', params.slug).fetch()

    // 2. Generate additional metadata
    const toc = generateTableOfContents(doc.content)
    const related = await $content('docs')
      .where({ tags: { $contains: doc.tags[0] } })
      .limit(3)
      .fetch()

    return {
      document: {
        ...doc,
        toc,
        related
      }
    }
  },
  // Client-side data supplementation
  data() {
    return {
      comments: [] // Client fetches comments
    }
  },
  mounted() {
    this.loadComments()
  },
  methods: {
    async loadComments() {
      this.comments = await this.$axios.$get(`/api/comments/${this.$route.params.slug}`)
    }
  }
})

Incremental Static Regeneration (ISR) Integration:

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    isr: {
      // Default configuration
      revalidate: 3600 // 1 hour
    }
  },
  // Dynamic route ISR configuration
  generate: {
    routes: async () => {
      // 1. Get static routes
      const staticRoutes = await getStaticRoutes()

      // 2. Add dynamic route placeholders
      const dynamicRoutes = await getDynamicRouteSlugs()

      return [...staticRoutes, ...dynamicRoutes.map(slug => `/blog/${slug}`)]
    }
  }
})

SSG Performance Optimization and Cache Strategies

Static Asset Optimization:

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    // Static asset CDN configuration
    cdnURL: process.env.CDN_URL,
    // Preload critical resources
    head: {
      link: [
        { rel: 'preload', href: '/_nuxt/runtime.js', as: 'script' },
        { rel: 'preload', href: '/_nuxt/commons.app.js', as: 'script' }
      ]
    }
  },
  // Build optimization
  build: {
    // Extract CSS to separate file
    extractCSS: true,
    // Optimize static assets
    optimization: {
      splitChunks: {
        chunks: 'all',
        automaticNameDelimiter: '.'
      }
    }
  }
})

Cache Control Strategy:

# Static asset response header example
Cache-Control: public, max-age=31536000, immutable
Content-Disposition: inline
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2022 07:28:00 GMT

SSG Security and Access Control

Static Site Security Strategy:

// server/middleware/security.ts
export default defineEventHandler(async (event) => {
  // 1. Content Security Policy
  setHeader(event, 'Content-Security-Policy', [
    "default-src 'self'",
    "script-src 'self' 'unsafe-inline' cdn.example.com",
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: cdn.example.com"
  ].join('; '))

  // 2. Subresource Integrity (SRI)
  if (event.node.req.url.includes('/_nuxt/')) {
    const integrity = generateSRIHash(event.node.req.url)
    setHeader(event, 'Integrity', integrity)
  }

  // 3. XSS Protection
  setHeader(event, 'X-XSS-Protection', '1; mode=block')
  setHeader(event, 'X-Content-Type-Options', 'nosniff')
})

Sensitive Content Handling:

<!-- pages/protected.vue -->
<script setup>
const { data } = await useAsyncData('protected-content', () => {
  return $fetch('/api/protected', {
    headers: {
      'Authorization': `Bearer ${useCookie('token').value}`
    }
  })
})

// Client-side conditional rendering
const showContent = computed(() => {
  return process.client && hasAccess(data.value)
})
</script>

<template>
  <div v-if="showContent">
    <!-- Sensitive content -->
  </div>
  <div v-else>
    <NuxtLink to="/login">Please log in to view content</NuxtLink>
  </div>
</template>

SSG Use Cases

Documentation Website Generation:

// nuxt.config.ts
export default defineNuxtConfig({
  // Document route generation
  generate: {
    routes: async () => {
      const { $content } = require('@nuxt/content')
      const files = await $content({ deep: true }).only(['path']).fetch()
      return files.map(file => file.path)
    }
  },
  // Document-specific configuration
  content: {
    documentDriven: true,
    navigation: {
      fields: ['title', 'description', 'slug']
    }
  }
})

Blog System Implementation:

// server/api/posts.ts
export default defineEventHandler(async (event) => {
  // 1. Pre-generate all posts during static generation
  if (process.static) {
    const posts = await getPostsFromDB()
    await generateStaticPosts(posts)
    return { status: 'generated' }
  }

  // 2. Dynamic request handling
  const { slug } = getQuery(event)
  if (slug) {
    return getPostBySlug(slug)
  }
  return getAllPosts()
})

Combining SSG and ISR

Hybrid Rendering Strategy:

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    isr: {
      // Global ISR configuration
      revalidate: 3600 // 1 hour
    }
  },
  routeRules: {
    // Static page + ISR fallback
    '/blog/:slug': {
      ssr: false, // Disable SSR
      isr: {
        revalidate: 300, // 5-minute revalidation
        swr: 3600 // 1-hour stale-while-revalidate
      }
    },
    // Critical page SSR + ISR
    '/products/:id': {
      ssr: true, // Enable SSR
      isr: {
        revalidate: 60, // 1-minute revalidation
        swr: 600 // 10-minute stale-while-revalidate
      }
    }
  }
})

Dynamic Content Staticization:

// server/routes/blog.js
export default defineRoutes({
  '/blog/:slug': {
    handler: async (req, res) => {
      // 1. Check if static file exists
      const staticFile = path.join(__dirname, '../dist/blog', req.params.slug, 'index.html')
      if (fs.existsSync(staticFile)) {
        return res.sendFile(staticFile)
      }

      // 2. Dynamically generate and cache
      const post = await getPostFromDB(req.params.slug)
      if (!post) throw createError(404)

      const html = await renderSSR('blog-post', { post })
      await cacheStaticPage(`/blog/${req.params.slug}`, html)

      res.send(html)
    },
    isr: {
      revalidate: 300 // 5-minute revalidation
    }
  }
})

Real-Time Application Development

Combining WebSocket with Nuxt.js

WebSocket Integration:

// plugins/websocket.ts
export default defineNuxtPlugin((nuxtApp) => {
  if (process.client) {
    const socket = new WebSocket(process.env.WS_URL)

    // Connection management
    const connection = {
      socket,
      connected: false,
      reconnectAttempts: 0,

      connect() {
        socket.onopen = () => {
          this.connected = true
          this.reconnectAttempts = 0
        }

        socket.onmessage = (event) => {
          const data = JSON.parse(event.data)
          nuxtApp.hook('websocket:message', data)
        }

        socket.onclose = () => {
          this.connected = false
          this.reconnect()
        }
      },

      reconnect() {
        if (this.reconnectAttempts < 5) {
          setTimeout(() => {
            this.reconnectAttempts++
            this.connect()
          }, 1000 * Math.pow(2, this.reconnectAttempts))
        }
      },

      send(data) {
        if (this.connected) {
          socket.send(JSON.stringify(data))
        } else {
          // Add to message queue
          this.messageQueue.push(data)
        }
      }
    }

    connection.connect()

    // Provide global access
    return {
      provide: {
        websocket: connection
      }
    }
  }
})

Real-Time Data Updates:

<!-- components/RealTimeFeed.vue -->
<script setup>
const feedItems = ref([])
const { $websocket } = useNuxtApp()

// Listen for WebSocket messages
onMounted(() => {
  $websocket.socket.onmessage = (event) => {
    const data = JSON.parse(event.data)
    if (data.type === 'FEED_UPDATE') {
      feedItems.value = [...feedItems.value, data.item]
    }
  }
})

// Manually send message
const sendMessage = (message) => {
  $websocket.send({
    type: 'NEW_MESSAGE',
    content: message
  })
}
</script>

<template>
  <div>
    <div v-for="item in feedItems" :key="item.id">
      {{ item.content }}
    </div>
    <input v-model="newMessage" @keyup.enter="sendMessage(newMessage)" />
  </div>
</template>

Combining GraphQL with Nuxt.js

Apollo Client Integration:

// plugins/apollo.ts
export default defineNuxtPlugin((nuxtApp) => {
  const apolloClient = new ApolloClient({
    link: createHttpLink({
      uri: process.env.GRAPHQL_ENDPOINT,
      headers: {
        Authorization: useCookie('token')?.value || ''
      }
    }),
    cache: new InMemoryCache(),
    ssrMode: process.server
  })

  return {
    provide: {
      apollo: apolloClient
    }
  }
})

SSR Data Fetching:

<!-- pages/products.vue -->
<script setup>
const { result, loading, error } = useQuery(gql`
  query GetProducts {
    products {
      id
      name
      price
    }
  }
`, null, {
  // SSR options
  server: true,
  prefetch: true
})

// Client-side cache strategy
const { data } = useApolloCache({
  query: gql`...`,
  update: (cache, { data }) => {
    cache.modify({
      fields: {
        products(existing = []) {
          return [...existing, ...data.newProducts]
        }
      }
    })
  }
})
</script>

Real-Time Collaborative Editing Implementation

CRDT Algorithm Integration:

// composables/useCollaborativeEditing.ts
export const useCollaborativeEditing = (docId: string) => {
  const localState = ref({})
  const remoteStates = ref(new Map<string, any>())

  // 1. Initialize CRDT document
  const crdt = new Y.Doc()
  const provider = new WebsocketProvider(
    process.env.WS_URL,
    'document-' + docId,
    crdt,
    { connect: false }
  )

  // 2. Connection management
  onMounted(() => {
    provider.connect()

    // Listen for remote changes
    provider.awareness.on('change', () => {
      const clients = provider.awareness.getStates()
      remoteStates.value = new Map(
        Object.entries(clients).map(([id, state]) => [id, state])
      )
    })

    // Apply remote changes
    crdt.on('update', (update) => {
      Y.applyUpdate(localState.value, update)
    })
  })

  // 3. Local edit handling
  const applyLocalChange = (change) => {
    Y.applyUpdate(crdt, change)
  }

  // 4. State synchronization
  const getState = () => {
    return {
      local: localState.value,
      remote: Object.fromEntries(remoteStates.value)
    }
  }

  return {
    state: getState(),
    applyChange: applyLocalChange
  }
}

OT Algorithm Implementation Example:

// server/ot-server.ts
export class OTServer {
  private documents: Map<string, TextOperation[]> = new Map()

  // Apply client operation
  applyOperation(docId: string, clientId: string, operation: TextOperation) {
    // 1. Get current document state
    let docOps = this.documents.get(docId) || []

    // 2. Transform operation to resolve conflicts
    const transformedOp = docOps.reduce((op, serverOp) => {
      return transformOperation(op, serverOp)
    }, operation)

    // 3. Update document state
    docOps.push(transformedOp)
    this.documents.set(docId, docOps)

    // 4. Broadcast to other clients
    this.broadcast(docId, clientId, transformedOp)
  }

  // Operation transformation algorithm
  private transformOperation(op1: TextOperation, op2: TextOperation): TextOperation {
    // Implement OT transformation logic
    // ...
  }
}

Performance Optimization for Real-Time Applications

WebSocket Connection Optimization:

// plugins/websocket-optimized.ts
export default defineNuxtPlugin((nuxtApp) => {
  // 1. Connection pool management
  const connectionPool = new Map<string, WebSocket>()

  // 2. Message batching
  const messageQueue = new Map<string, any[]>()
  let batchTimer: number

  const flushBatch = (channel: string) => {
    if (messageQueue.has(channel)) {
      const messages = messageQueue.get(channel)
      const ws = connectionPool.get(channel)
      if (ws && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify(messages))
      }
      messageQueue.delete(channel)
    }
  }

  // 3. Heartbeat detection
  const startHeartbeat = (ws: WebSocket) => {
    const interval = setInterval(() => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: 'HEARTBEAT' }))
      } else {
        clearInterval(interval)
      }
    }, 30000)

    return () => clearInterval(interval)
  }

  return {
    provide: {
      optimizedWebSocket: {
        send(channel: string, message: any) {
          // Batch messages
          if (!messageQueue.has(channel)) {
            messageQueue.set(channel, [])
            batchTimer = setTimeout(() => flushBatch(channel), 50)
          }
          messageQueue.get(channel).push(message)
        },

        connect(channel: string) {
          // Connection reuse
          if (!connectionPool.has(channel)) {
            const ws = new WebSocket(`${process.env.WS_URL}/${channel}`)
            connectionPool.set(channel, ws)

            // Start heartbeat
            const stopHeartbeat = startHeartbeat(ws)

            ws.onclose = () => {
              connectionPool.delete(channel)
              stopHeartbeat()
            }
          }
        }
      }
    }
  }
})

Data Compression and Binary Transmission:

// server/websocket-compression.ts
export class CompressedWebSocket {
  private ws: WebSocket
  private compressor = new zlib.BrotliCompress()
  private decompressor = new zlib.BrotliDecompress()

  constructor(ws: WebSocket) {
    this.ws = ws
    this.setupHandlers()
  }

  private setupHandlers() {
    this.ws.on('message', (data) => {
      // Decompress message
      this.decompressor.write(data, (err, decompressed) => {
        if (!err) {
          const message = JSON.parse(decompressed.toString())
          this.handleMessage(message)
        }
      })
    })

    // Send compressed message
    this.send = (message) => {
      const json = JSON.stringify(message)
      this.compressor.write(json, (err, compressed) => {
        if (!err) {
          this.ws.send(compressed)
        }
      })
    }
  }
}

Security and Access Control for Real-Time Applications

WebSocket Authentication:

// server/middleware/ws-auth.ts
export default defineEventHandler(async (event) => {
  if (event.node.req.headers['upgrade']?.toLowerCase() !== 'websocket') {
    return // Skip non-WebSocket requests
  }

  // 1. Get token from query parameters
  const token = getQuery(event).token as string

  // 2. Verify JWT
  try {
    const decoded = verifyJWT(token)
    event.context.user = decoded

    // 3. Bind user to WebSocket
    event.node.req.websocketUser = decoded
  } catch (err) {
    throw createError({
      statusCode: 401,
      message: 'WebSocket authentication failed'
    })
  }
})

Real-Time Data Permission Filtering:

// server/ws-handlers.ts
export const handleRealtimeUpdate = (user: User, data: any) => {
  // 1. Data permission check
  if (data.type === 'DOCUMENT_UPDATE') {
    if (!hasDocumentAccess(user.id, data.documentId)) {
      throw new Error('Unauthorized document access')
    }
  }

  // 2. Field-level permission filtering
  const filteredData = filterSensitiveFields(data, user.permissions)

  // 3. Broadcast to authorized users
  broadcastToAuthorizedUsers(
    data.targetUsers || [user.id],
    filteredData
  )
}

// Client-side permission verification
export const useRealtimePermission = (requiredPermission: string) => {
  const { $websocket } = useNuxtApp()
  const hasPermission = ref(false)

  onMounted(async () => {
    const userPermissions = await $websocket.fetchUserPermissions()
    hasPermission.value = userPermissions.includes(requiredPermission)
  })

  return { hasPermission }
}
Share your love