Nuxt.js es un framework de Vue.js para crear aplicaciones universales, estáticas y de servidor (SSR). Con TypeScript, obtienes un sistema de tipos robusto para tu aplicación.
# Crear nuevo proyecto
npx create-nuxt-app mi-app-nuxt
# Durante la creación, seleccionar TypeScript
? Programming language: TypeScript
? Will you use TypeScript or JavaScript? TypeScript
# Estructura básica del proyecto
.
├── components/
├── layouts/
├── pages/
├── plugins/
├── static/
├── store/
├── nuxt.config.ts # Configuración de Nuxt
├── tsconfig.json # Configuración de TypeScript
└── package.json
import { NuxtConfig } from '@nuxt/types'
const config: NuxtConfig = {
// Mode: 'universal' o 'spa'
ssr: true,
// Configuración de TypeScript
typescript: {
typeCheck: {
eslint: true
}
},
// Módulos
modules: [
'@nuxtjs/axios',
'@nuxtjs/auth-next'
],
// Configuración de Axios
axios: {
baseURL: process.env.API_URL || 'https://api.example.com'
},
// Configuración de compilación
build: {
transpile: ['vee-validate/dist/rules'],
loaders: {
scss: { additionalData: `@import "~/assets/scss/variables.scss";` }
}
},
// Variables de entorno
publicRuntimeConfig: {
appName: process.env.APP_NAME || 'Mi App Nuxt'
}
}
export default config
{
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"moduleResolution": "Node",
"lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@/*": ["./*"]
},
"types": ["@types/node", "@nuxt/types"]
},
"exclude": ["node_modules"]
}
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
@Component
export default class MyComponent extends Vue {
// Propiedades reactivas
private counter: number = 0
private message: string = 'Hola Nuxt!'
// Props
@Prop({ type: String, required: true }) readonly title!: string
@Prop({ default: 30 }) readonly age!: number
// Computed
get reversedMessage(): string {
return this.message.split('').reverse().join('')
}
// Métodos
increment(): void {
this.counter++
}
// Hooks del ciclo de vida
mounted(): void {
console.log('Componente montado')
}
}
</script>
<script lang="ts">
import { defineComponent, ref, computed, onMounted } from '@nuxtjs/composition-api'
export default defineComponent({
props: {
title: {
type: String,
required: true
},
age: {
type: Number,
default: 30
}
},
setup(props, { emit }) {
const counter = ref(0)
const message = ref('Hola Nuxt!')
const reversedMessage = computed(() => {
return message.value.split('').reverse().join('')
})
function increment() {
counter.value++
emit('incremented', counter.value)
}
onMounted(() => {
console.log('Componente montado')
})
return {
counter,
message,
reversedMessage,
increment
}
}
})
</script>
// store/modules/user.ts
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
import { User } from '~/types/user'
@Module({
name: 'user',
stateFactory: true,
namespaced: true
})
export default class UserModule extends VuexModule {
private user: User | null = null
private token: string | null = null
get isAuthenticated(): boolean {
return !!this.token
}
get currentUser(): User | null {
return this.user
}
@Mutation
SET_USER(user: User): void {
this.user = user
}
@Mutation
SET_TOKEN(token: string): void {
this.token = token
}
@Action
async login({ email, password }: { email: string; password: string }) {
const response = await this.$axios.$post('/auth/login', { email, password })
this.SET_TOKEN(response.token)
this.SET_USER(response.user)
}
@Action
logout(): void {
this.SET_TOKEN(null)
this.SET_USER(null)
}
}
// composables/useUserStore.ts
import { computed } from '@nuxtjs/composition-api'
import { useStore } from '~/store'
export const useUserStore = () => {
const store = useStore()
const isAuthenticated = computed(() => store.state.user.token !== null)
const currentUser = computed(() => store.state.user.user)
const login = async (email: string, password: string) => {
const response = await store.$axios.$post('/auth/login', { email, password })
store.commit('user/SET_TOKEN', response.token)
store.commit('user/SET_USER', response.user)
}
const logout = () => {
store.commit('user/SET_TOKEN', null)
store.commit('user/SET_USER', null)
}
return {
isAuthenticated,
currentUser,
login,
logout
}
}
// types/router.d.ts
import { Route } from 'vue-router'
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter
$route: Route & {
params: {
id?: string
slug?: string
}
}
}
}
// Uso en componentes
this.$route.params.id // Tipado como string | undefined
// middleware/auth.ts
import { Context } from '@nuxt/types'
export default function ({ store, redirect }: Context) {
// Si el usuario no está autenticado
if (!store.state.user.token) {
return redirect('/login')
}
}
// Uso en rutas
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
middleware: 'auth',
// ...
})
</script>
// composables/usePosts.ts
import { useAsync, useContext } from '@nuxtjs/composition-api'
interface Post {
id: number
title: string
body: string
userId: number
}
export const usePosts = () => {
const { $axios } = useContext()
const fetchPosts = useAsync(async () => {
const posts = await $axios.$get<Post[]>('/posts')
return posts
})
return {
fetchPosts
}
}
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
import { Post } from '~/types'
export default defineComponent({
async asyncData({ $axios }) {
const posts = await $axios.$get<Post[]>('/posts')
return { posts }
},
data() {
return {
posts: [] as Post[]
}
},
async fetch() {
this.posts = await this.$axios.$get<Post[]>('/posts')
}
})
</script>
// plugins/axios-accessor.ts
import { Plugin } from '@nuxt/types'
import { initializeAxios } from '~/utils/api'
const accessor: Plugin = ({ $axios }) => {
initializeAxios($axios)
}
export default accessor
// types/vue.d.ts
import { AxiosInstance } from 'axios'
declare module 'vue/types/vue' {
interface Vue {
$axios: AxiosInstance
}
}
declare module '@nuxt/types' {
interface NuxtAppOptions {
$axios: AxiosInstance
}
}
// nuxt.config.ts
export default {
modules: [
['@nuxtjs/axios', {
proxy: true,
credentials: true
}],
['@nuxtjs/auth-next', {
strategies: {
local: {
token: {
property: 'token',
required: true,
type: 'Bearer'
},
user: {
property: 'user',
autoFetch: true
},
endpoints: {
login: { url: '/auth/login', method: 'post' },
logout: { url: '/auth/logout', method: 'post' },
user: { url: '/auth/user', method: 'get' }
}
}
}
}]
]
}
// tests/componentes/Button.spec.ts
import { mount } from '@vue/test-utils'
import Button from '~/components/Button.vue'
describe('Button', () => {
test('emits click event', () => {
const wrapper = mount(Button, {
propsData: {
label: 'Click me'
}
})
wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
})
// jest.config.js
module.exports = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^~/(.*)$': '<rootDir>/$1'
},
moduleFileExtensions: ['js', 'ts', 'json', 'vue'],
transform: {
'^.+\\.ts$': 'ts-jest',
'^.+\\.vue$': 'vue-jest'
},
collectCoverage: true,
collectCoverageFrom: [
'components/**/*.vue',
'pages/**/*.vue'
]
}
// tests/e2e/specs/login.spec.ts
describe('Login', () => {
it('successfully logs in', () => {
cy.visit('/login')
cy.get('[data-test="email"]').type('user@example.com')
cy.get('[data-test="password"]').type('password123')
cy.get('[data-test="submit"]').click()
cy.url().should('include', '/dashboard')
})
})
// nuxt.config.ts
export default {
target: 'static',
generate: {
routes: [
'/about',
'/contact',
async () => {
const posts = await $axios.$get('/posts')
return posts.map(post => `/posts/${post.id}`)
}
]
}
}
// Generar la aplicación
npm run generate
Nuxt.js con TypeScript ofrece una combinación poderosa para construir aplicaciones Vue.js universales, estáticas o de servidor con un sistema de tipos robusto.