1. Renderless Components
<!-- components/FetchData.vue — renderless -->
<script setup lang="ts" generic="T">
import { ref, watchEffect } from 'vue'
const props = defineProps<{ url: string }>()
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(true)
watchEffect(async () => {
loading.value = true
try {
const res = await fetch(props.url)
data.value = await res.json()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
})
</script>
<template>
<slot :data="data" :error="error" :loading="loading" />
</template>
<!-- Usage -->
<FetchData url="/api/users" v-slot="{ data: users, loading }">
<div v-if="loading">Loading...</div>
<ul v-else>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</FetchData>
2. Render Functions
import { h, defineComponent } from 'vue'
// Render function component
export const DynamicHeading = defineComponent({
props: {
level: { type: Number, required: true, validator: (v: number) => v >= 1 && v <= 6 },
},
setup(props, { slots }) {
return () => h(`h${props.level}`, {
class: 'heading',
}, slots.default?.())
},
})
// Functional component
export function SmartLink(props: { href: string }, { slots }: any) {
const isExternal = props.href.startsWith('http')
if (isExternal) {
return h('a', {
href: props.href,
target: '_blank',
rel: 'noopener noreferrer',
}, slots.default?.())
}
return h(resolveComponent('NuxtLink'), { to: props.href }, slots.default?.())
}
3. Higher-Order Components
// composables/withAuth.ts
import { defineComponent, h } from 'vue'
export function withAuth(WrappedComponent: any) {
return defineComponent({
setup(props, { attrs, slots }) {
const { loggedIn } = useUserSession()
return () => {
if (!loggedIn.value) {
return h('div', { class: 'auth-required' }, 'Vui lòng đăng nhập')
}
return h(WrappedComponent, { ...props, ...attrs }, slots)
}
},
})
}
// Usage
const ProtectedDashboard = withAuth(Dashboard)
4. Creating Nuxt Module
// modules/analytics/index.ts
import { defineNuxtModule, addPlugin, addServerHandler, createResolver } from '@nuxt/kit'
export interface ModuleOptions {
trackingId: string
debug?: boolean
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'my-analytics',
configKey: 'analytics',
compatibility: { nuxt: '>=3.0.0' },
},
defaults: {
trackingId: '',
debug: false,
},
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)
// Validate config
if (!options.trackingId) {
console.warn('[analytics] trackingId is required')
return
}
// Add runtime config
nuxt.options.runtimeConfig.public.analytics = {
trackingId: options.trackingId,
debug: options.debug,
}
// Add client plugin
addPlugin(resolver.resolve('./runtime/plugin.client'))
// Add server route
addServerHandler({
route: '/api/analytics/track',
handler: resolver.resolve('./runtime/server/track.post'),
})
// Add composable
addImports({
name: 'useAnalytics',
from: resolver.resolve('./runtime/composables/useAnalytics'),
})
},
})
5. Nuxt Layers
// nuxt.config.ts — extend from layers
export default defineNuxtConfig({
extends: [
'./layers/base', // Local layer
'github:org/shared-layer', // Remote layer
],
})
// layers/base/nuxt.config.ts
export default defineNuxtConfig({
components: [{ path: './components', prefix: 'Base' }],
css: ['./assets/base.css'],
})
Bài tiếp theo: Testing Vue & Nuxt — Vitest, Vue Test Utils, Playwright.