Skip to content

Crear proyecto ​

Vas a crear tu primer proyecto profesional de Vue con create-vue (basado en Vite), entender la estructura generada y la anatomía de un Single File Component (.vue): plantilla, lógica y estilos en un mismo archivo.

Concepto ​

create-vue es el asistente oficial. Crea un proyecto con Vite (servidor de desarrollo instantáneo y build optimizado) y te pregunta qué herramientas del ecosistema quieres agregar: TypeScript, Vue Router, Pinia, pruebas, ESLint...

Requisitos: Node.js 20.19+ o 22.12+ (instalación).

Crear el proyecto ​

bash
npm create vue@latest

El asistente pregunta (respuestas sugeridas para este curso):

txt
✔ Project name: … mi-app-vue
✔ Add TypeScript? … No
✔ Select features to include in your project:
    ◉ Router (SPA development)
    ◉ Pinia (state management)
    ◯ Vitest (unit testing)
    ◯ End-to-End Testing
    ◉ ESLint (error prevention)
    ◉ Prettier (code formatting)
bash
cd mi-app-vue
npm install
npm run dev

Abre http://localhost:5173.

TIP

Si ya dominas TypeScript, elige "Yes": Vue tiene un soporte excelente y los ejemplos de este curso funcionan igual agregando lang="ts" al script.

Estructura generada ​

txt
mi-app-vue/
├── public/
│   └── favicon.ico
├── src/
│   ├── assets/            ← CSS e imágenes que importas desde el código
│   │   ├── base.css
│   │   └── main.css
│   ├── components/        ← componentes reutilizables
│   │   └── HelloWorld.vue
│   ├── router/
│   │   └── index.js       ← rutas (Vue Router)
│   ├── stores/
│   │   └── counter.js     ← estado global (Pinia)
│   ├── views/             ← páginas (una por ruta)
│   │   ├── HomeView.vue
│   │   └── AboutView.vue
│   ├── App.vue            ← componente raíz
│   └── main.js            ← punto de entrada
├── index.html
├── package.json
├── vite.config.js
└── eslint.config.js

Los archivos clave ​

js
import './assets/main.css'

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

// Crea la aplicación, le agrega plugins y la monta en <div id="app">
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
html
<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mi app Vue</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

export default defineConfig({
  plugins: [vue(), vueDevTools()],
  resolve: {
    alias: {
      // "@/components/X.vue" apunta a "src/components/X.vue"
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})
json
{
  "name": "mi-app-vue",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint . --fix",
    "format": "prettier --write src/"
  },
  "dependencies": {
    "pinia": "^3.0.0",
    "vue": "^3.5.0",
    "vue-router": "^4.5.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^6.0.0",
    "vite": "^7.0.0",
    "vite-plugin-vue-devtools": "^8.0.0"
  }
}
ComandoQué hace
npm run devServidor de desarrollo con recarga instantánea
npm run buildBuild optimizado en dist/
npm run previewSirve dist/ para probar el build
npm run lintRevisa y corrige errores

Anatomía de un Single File Component ​

Un archivo .vue tiene hasta tres bloques:

vue
<!-- src/components/TarjetaPerfil.vue -->

<!-- 1. Lógica: <script setup> -->
<script setup>
import { ref, computed } from 'vue'

// Todo lo que declares aquí está disponible en la plantilla
const nombre = ref('Laura Méndez')
const puesto = ref('Desarrolladora Frontend')
const seguidores = ref(1280)
const siguiendo = ref(false)

const textoBoton = computed(() => (siguiendo.value ? 'Siguiendo ✓' : 'Seguir'))

function alternar() {
  siguiendo.value = !siguiendo.value
  seguidores.value += siguiendo.value ? 1 : -1
}
</script>

<!-- 2. Plantilla: el HTML del componente -->
<template>
  <article class="tarjeta">
    <img class="avatar" src="https://i.pravatar.cc/120?img=47" :alt="`Foto de ${nombre}`">
    <h2>{{ nombre }}</h2>
    <p class="puesto">{{ puesto }}</p>
    <p>{{ seguidores.toLocaleString('es-MX') }} seguidores</p>
    <button :class="{ activo: siguiendo }" @click="alternar">{{ textoBoton }}</button>
  </article>
</template>

<!-- 3. Estilos: scoped = sólo afectan a este componente -->
<style scoped>
.tarjeta {
  max-width: 280px;
  padding: 1.5rem;
  text-align: center;
  border-radius: 16px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.08);
  font-family: system-ui, sans-serif;
}
.avatar { width: 96px; height: 96px; border-radius: 50%; }
.puesto { color: #6b7280; }
button {
  padding: 0.5rem 1.25rem;
  border: 2px solid #42b883;
  border-radius: 999px;
  background: white;
  color: #42b883;
  font-weight: 600;
  cursor: pointer;
}
button.activo { background: #42b883; color: white; }
</style>

Usarlo en otro componente ​

Con <script setup>, basta con importarlo: queda disponible en la plantilla.

vue
<!-- src/App.vue -->
<script setup>
import TarjetaPerfil from './components/TarjetaPerfil.vue'
</script>

<template>
  <main style="display: grid; place-items: center; min-height: 100vh">
    <TarjetaPerfil />
  </main>
</template>

Estilos con scoped ​

Vue agrega un atributo único (por ejemplo data-v-7a8b9c) a los elementos del componente y reescribe los selectores. Así, la clase .tarjeta de este componente no choca con otra .tarjeta de otro archivo.

vue
<style scoped>
/* Para dar estilo a un elemento dentro de un componente hijo */
.contenedor :deep(.titulo) { color: #42b883; }
</style>

<style>
/* Sin scoped: estilos globales (úsalo con moderación) */
body { margin: 0; }
</style>

También puedes usar CSS Modules (<style module>), Sass (<style lang="scss">, instalando sass) o Tailwind CSS.

Tailwind CSS v4 en Vue ​

bash
npm i tailwindcss @tailwindcss/vite
js
// vite.config.js
import tailwindcss from '@tailwindcss/vite'
// plugins: [vue(), vueDevTools(), tailwindcss()]
css
/* src/assets/main.css */
@import "tailwindcss";

Limpiar el proyecto ​

Para empezar desde cero, deja App.vue mínimo y borra los componentes de ejemplo:

vue
<!-- src/App.vue -->
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>

<template>
  <header>
    <nav>
      <RouterLink to="/">Inicio</RouterLink> |
      <RouterLink to="/about">Acerca de</RouterLink>
    </nav>
  </header>
  <!-- Aquí se muestra la vista de la ruta actual -->
  <RouterView />
</template>

Herramientas recomendadas ​

  • VS Code con la extensión oficial Vue - Official (antes Volar): resaltado, autocompletado y errores en archivos .vue.
  • Vue DevTools: ya viene integrado como plugin de Vite (vite-plugin-vue-devtools); también existe como extensión del navegador. Muestra componentes, estado de Pinia, rutas y eventos.

Buena práctica

  • Nombra los componentes en PascalCase y con al menos dos palabras (TarjetaPerfil.vue, no Tarjeta.vue) para evitar choques con etiquetas HTML presentes o futuras.
  • Las páginas (una por ruta) van en views/; las piezas reutilizables, en components/.

Error común

Olvidar la extensión .vue al importar: import Tarjeta from './Tarjeta' falla. Escribe siempre ./Tarjeta.vue.

Ejercicios ​

  1. Crea un proyecto con create-vue (con Router y Pinia), limpia los ejemplos y crea un componente TarjetaProducto.vue con nombre, precio y un botón "Agregar" que cambie su texto a "Agregado ✓" al hacer clic.
  2. Muestra tres TarjetaProducto en App.vue y verifica en Vue DevTools que cada una tiene su propio estado.
Ver solución
vue
<script setup>
import { ref } from 'vue'

const agregado = ref(false)
</script>

<template>
  <article class="producto">
    <h3>Café de Chiapas</h3>
    <p>$249 MXN</p>
    <button :disabled="agregado" @click="agregado = true">
      {{ agregado ? 'Agregado ✓' : 'Agregar' }}
    </button>
  </article>
</template>

<style scoped>
.producto { border: 1px solid #e5e7eb; border-radius: 12px; padding: 1rem; }
button { background: #42b883; color: white; border: 0; padding: 0.5rem 1rem; border-radius: 8px; }
button:disabled { opacity: 0.7; }
</style>
vue
<script setup>
import TarjetaProducto from './components/TarjetaProducto.vue'
</script>

<template>
  <main style="display: flex; gap: 1rem; padding: 2rem">
    <TarjetaProducto />
    <TarjetaProducto />
    <TarjetaProducto />
  </main>
</template>

En la lección de componentes aprenderás a pasar nombre y precio distintos a cada tarjeta con props.

Resumen ​

  • npm create vue@latest crea un proyecto con Vite y, opcionalmente, Router, Pinia y TypeScript.
  • main.js crea la app con createApp, registra plugins y la monta en #app.
  • Un Single File Component tiene <script setup>, <template> y <style scoped>.
  • Con <script setup>, lo que declaras e importas está disponible directamente en la plantilla.

Academia Web — aprende, copia y construye.