Efficlose

Advanced Topics

Advanced features and techniques for power users.

Server Routes

API Endpoints

Create API routes in server/api/:

// server/api/hello.ts
export default defineEventHandler((event) => {
  return {
    message: 'Hello from API!'
  }
})

Access at /api/hello.

Dynamic Routes

// server/api/posts/[id].ts
export default defineEventHandler((event) => {
  const id = getRouterParam(event, 'id')
  
  return {
    id,
    title: 'Post Title'
  }
})

Request Body

// server/api/posts/create.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  
  // Process body
  return {
    success: true,
    data: body
  }
})

Middleware

Route Middleware

Create middleware in middleware/:

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const user = useUser()
  const config = useRuntimeConfig()
  
  if (!user.value) {
    return navigateTo(config.public.loginUrl, { external: true })
  }
})

Use in pages:

<script setup lang="ts">
definePageMeta({
  middleware: 'auth'
})
</script>

Global Middleware

// middleware/analytics.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  // Track page view
  console.log('Navigating to:', to.path)
})

Plugins

Client Plugin

// plugins/my-plugin.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Only runs on client
  console.log('Client plugin loaded')
  
  return {
    provide: {
      myFunction: () => 'Hello!'
    }
  }
})

Server Plugin

// plugins/my-plugin.server.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Only runs on server
  console.log('Server plugin loaded')
})

Use in Components

<script setup lang="ts">
const { $myFunction } = useNuxtApp()

console.log($myFunction()) // 'Hello!'
</script>

State Management

useState

Share state across components:

// composables/useCounter.ts
export const useCounter = () => {
  const count = useState('counter', () => 0)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  
  return {
    count,
    increment,
    decrement
  }
}

Use in components:

<script setup lang="ts">
const { count, increment } = useCounter()
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <UButton @click="increment">Increment</UButton>
  </div>
</template>

Pinia (Optional)

Install Pinia for complex state:

npm install pinia @pinia/nuxt

Configure:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt']
})

Create store:

// stores/user.ts
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    email: ''
  }),
  actions: {
    setUser(user: { name: string; email: string }) {
      this.name = user.name
      this.email = user.email
    }
  }
})

Custom Composables

Reusable Logic

// composables/useFetch.ts
export const useApiFetch = <T>(url: string) => {
  return useFetch<T>(url, {
    baseURL: 'https://api.example.com',
    headers: {
      'Authorization': `Bearer ${useToken().value}`
    }
  })
}

With TypeScript

// composables/useUser.ts
interface User {
  id: number
  name: string
  email: string
}

export const useUser = () => {
  const user = useState<User | null>('user', () => null)
  
  const fetchUser = async () => {
    const { data } = await useApiFetch<User>('/user')
    user.value = data.value
  }
  
  return {
    user,
    fetchUser
  }
}

Performance

Lazy Loading

<script setup lang="ts">
const HeavyComponent = defineAsyncComponent(() =>
  import('~/components/HeavyComponent.vue')
)
</script>

<template>
  <Suspense>
    <HeavyComponent />
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

Image Optimization

<template>
  <NuxtImg
    src="/image.jpg"
    width="800"
    height="600"
    format="webp"
    quality="80"
    loading="lazy"
  />
</template>

Code Splitting

// nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            'vendor': ['vue', 'vue-router']
          }
        }
      }
    }
  }
})

Testing

Unit Tests

Install Vitest:

npm install -D vitest @vue/test-utils

Create test:

// components/Button.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Button from './Button.vue'

describe('Button', () => {
  it('renders properly', () => {
    const wrapper = mount(Button, {
      props: { label: 'Click me' }
    })
    expect(wrapper.text()).toContain('Click me')
  })
})

E2E Tests

Install Playwright:

npm install -D @playwright/test

Create test:

// tests/e2e/home.spec.ts
import { test, expect } from '@playwright/test'

test('homepage loads', async ({ page }) => {
  await page.goto('/')
  await expect(page.locator('h1')).toContainText('Welcome')
})

Security

CSRF Protection

// server/middleware/csrf.ts
export default defineEventHandler((event) => {
  if (event.method !== 'GET') {
    const token = getCookie(event, 'csrf-token')
    const headerToken = getHeader(event, 'x-csrf-token')
    
    if (token !== headerToken) {
      throw createError({
        statusCode: 403,
        message: 'Invalid CSRF token'
      })
    }
  }
})

Rate Limiting

// server/middleware/rate-limit.ts
const requests = new Map()

export default defineEventHandler((event) => {
  const ip = getRequestIP(event)
  const now = Date.now()
  const windowMs = 60000 // 1 minute
  const max = 100 // max requests per window
  
  const userRequests = requests.get(ip) || []
  const recentRequests = userRequests.filter(
    (time: number) => now - time < windowMs
  )
  
  if (recentRequests.length >= max) {
    throw createError({
      statusCode: 429,
      message: 'Too many requests'
    })
  }
  
  recentRequests.push(now)
  requests.set(ip, recentRequests)
})

Best Practices

Use server routes for sensitive operations and API calls to keep credentials secure.
Always validate and sanitize user input on the server side.
Leverage Nuxt's auto-imports for cleaner code and better tree-shaking.