NuxtJS
Nuxt es un marco de código abierto bajo licencia MIT para crear aplicaciones web modernas y de alto rendimiento que se pueden implementar en cualquier plataforma que ejecute JavaScript.
 ¿Qué es Nuxt?
 Para comprender qué es Nuxt, debemos comprender qué necesitamos para crear una aplicación moderna:
- Marco de JavaScript: Un marco de JavaScript para brindar reactividad y componentes web, elegimos Vue.js.
 - Webpack y Vite: Un paquete para admitir el reemplazo de módulos calientes en desarrollo y agrupar su código para producción, admitimos webpack 5 y Vite.
 - Última sintaxis de JavaScript: Un transpilador para escribir la última sintaxis de JavaScript mientras se admite navegadores heredados, usamos esbuild para eso.
 - Lado del servidor: Un servidor para servir su aplicación en desarrollo, pero también para admitir la representación del lado del servidor o las rutas API, Nuxt usa h3 para la versatilidad de implementación, como sin servidor, trabajadores, Node.js y un rendimiento inigualable.
 - Biblioteca de enrutamiento: Una biblioteca de enrutamiento para manejar la navegación del lado del cliente, elegimos vue-router.
 
Esto es solo la punta del iceberg, imagine tener que configurar todo esto para su proyecto, hacer que funcione y luego mantenerlo a lo largo del tiempo. Hemos estado haciendo esto desde octubre de 2016, ajustando todas las configuraciones para brindar la mejor optimización y rendimiento para cualquier aplicación de Vue.
Nuxt se encarga de esto y proporciona funcionalidad tanto de frontend como de backend para que pueda concentrarse en lo que importa: crear su aplicación web.
https://nuxt.com/docs/getting-started/introduction
https://nuxt.com/docs/getting-started/installation
Instalación
- Crear proyecto con el siguente comando
 
1
  | npx nuxi init nombre-proyecto
  | 
- Entrar a la carpeta e instalar dependencias
 
- La estructura inicial de Nuxt incluye un archivo typeScript llamado 
nuxt.config.ts el cual es recomendado dejarlo con dicha extension, los demas archivos puedes ser de tipo javascript - Probar la aplicacion
 
1
2
3
4
5
6
  | <template>
  <div>
    <p>Hello World!</p>
  </div>
</template>
 | 
Agregando paginas
- Nuxt permite la creacion dinamica de contenido dependiendo de lo que se vaya necesitando
 - Eliminar 
app.vue - Crear carpeta 
pages en la raiz del proyecto - Crear 
pages/index.vue   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  | <template>
  <div>
    <h2>Inicio</h2>
    <p>
      Lorem ipsum dolor sit amet consectetur, adipisicing elit. Obcaecati,
      placeat, quasi, saepe perspiciatis facilis possimus delectus excepturi
      officia ex ullam adipisci? Ratione repudiandae et doloribus numquam, quasi
      quam ullam cum.
    </p>
  </div>
</template>
<script setup>
</script>
<style scoped>
h2 {
  margin-bottom: 20px;
  font-size: 36px;
}
p {
  margin: 20px 0;
}
</style>
 | 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  | <template>
  <div>
    <h2>Acerca de...</h2>
    <p>
      Lorem ipsum dolor sit amet consectetur, adipisicing elit. Obcaecati,
      placeat, quasi, saepe perspiciatis facilis possimus delectus excepturi
      officia ex ullam adipisci? Ratione repudiandae et doloribus numquam, quasi
      quam ullam cum.
    </p>
  </div>
</template>
<script setup>
</script>
<style scoped>
h2 {
  margin-bottom: 20px;
  font-size: 36px;
}
p {
  margin: 20px 0;
}
</style>
 | 
- Crear componente 
pages/productos/index.vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  | <template>
  <div>
    <h2>Productos</h2>
  </div>
</template>
<script setup>
</script>
<style scoped>
h2 {
  margin-bottom: 20px;
  font-size: 36px;
}
p {
  margin: 20px 0;
}
</style>
 | 
Parametros de rutas
- Crear componente 
productos/[id].vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  | <template>
  <div>
    <p>Detalles para el producto con id: { { id_producto } }</p><br>
    <p>
      Lorem, ipsum dolor sit amet consectetur adipisicing elit. Ratione
      consectetur voluptatum magnam quasi, est dolorum. Quo saepe minus
      voluptate nemo?
    </p>
  </div>
</template>
<script setup>
const { id_producto } = useRoute().params;
</script>
<style scoped>
</style>
 | 
Navegación (NuxtLink)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  | <template>
  <div>
    <header>
      <nav>
        <NuxtLink to="/"><img src="~/assets/images/logo.png" /></NuxtLink>
        <ul>
          <li><NuxtLink to="/">Inicio</NuxtLink></li>
          <li><NuxtLink to="/about">Acerca de</NuxtLink></li>
          <li><NuxtLink to="/productos">Productos</NuxtLink></li>
        </ul>
      </nav>
    </header>
    <h2>Inicio</h2>
    <p>
      Lorem ipsum dolor sit amet consectetur, adipisicing elit. Obcaecati,
      placeat, quasi, saepe perspiciatis facilis possimus delectus excepturi
      officia ex ullam adipisci? Ratione repudiandae et doloribus numquam, quasi
      quam ullam cum.
    </p>
  </div>
</template>
<script setup>
</script>
<style scoped>
h2 {
  margin-bottom: 20px;
  font-size: 36px;
}
p {
  margin: 20px 0;
}
</style>
 | 
Plantillas/diseños
- Crear componente 
layouts/default.vue 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  | <template>
  <div>
    <header>
      <nav>
        <NuxtLink to="/">
          <img src="~/assets/images/logo.png" />
        </NuxtLink>
        <ul>
          <li><NuxtLink to="/">Inicio</NuxtLink></li>
          <li><NuxtLink to="/about">Acerca de</NuxtLink></li>
          <li><NuxtLink to="/productos">Productos</NuxtLink></li>
        </ul>
      </nav>
    </header>
    <div>
      <slot />
    </div>
  </div>
</template>
<style scoped>
.router-link-exact-active {
  color: fuchsia;
}
</style>
 | 
Plantilla/diseño personalizado
- Crear componente 
layouts/productos-layout.vue 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  | <template>
  <div>
    <header>
      <nav>
        <NuxtLink to="/productos">
          <img src="~/assets/images/logo.png" />
        </NuxtLink>
        <ul>
          <li><NuxtLink to="/">Inicio</NuxtLink></li>
          <li><NuxtLink to="/about">Acerca de</NuxtLink></li>
          <li><NuxtLink to="/productos">Productos</NuxtLink></li>
        </ul>
      </nav>
    </header>
    <div>
      <slot />
    </div>
    <footer>
      <ul>
        <li><NuxtLink to="/">Inicio</NuxtLink></li>
        <li><NuxtLink to="/about">Acerca de</NuxtLink></li>
        <li><NuxtLink to="/productos">Productos</NuxtLink></li>
      </ul>
    </footer>
  </div>
</template>
<style scoped>
.router-link-exact-active {
  color: fuchsia;
}
</style>
 | 
- Modificar 
productos/index.vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  | <template>
  <div>
    <h2>Productos</h2>
  </div>
</template>
<script setup>
definePageMeta({
  layout: "productos-layout",
})
</script>
<style scoped>
h2 {
  margin-bottom: 20px;
  font-size: 36px;
}
p {
  margin: 20px 0;
}
</style>
 | 
- Modificar 
productos/[id_producto].vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  | <template>
  <div>
    <p>Detalles para el producto con id: { { id_producto } } </p><br>
    <p>
      Lorem, ipsum dolor sit amet consectetur adipisicing elit. Ratione
      consectetur voluptatum magnam quasi, est dolorum. Quo saepe minus
      voluptate nemo?
    </p>
  </div>
</template>
<script setup>
const { id_producto } = useRoute().params
definePageMeta({
  layout: "productos-layout",
})
</script>
<style scoped>
</style>
 | 
Integrando TailwindCSS
https://nuxt.com/modules
https://nuxt.com/modules/tailwindcss
1
2
  | npm install --save-dev @nuxtjs/tailwindcss
  | 
- Agregar el modulo en 
nuxt.config.ts 
1
2
3
4
  | export default defineNuxtConfig({
    modules: ['@nuxtjs/tailwindcss']
})
 | 
- Modificar 
layouts/default.vue 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  | <template>
  <div>
    <header class="shadow-sm bg-sky-900">
      <nav class="container mx-auto p-4 flex items-center justify-between">
        <NuxtLink to="/">
          <img class="w-auto h-10" src="~/assets/images/logo.png" />
        </NuxtLink>
        <ul class="flex gap-4">
          <li>
            <NuxtLink class="text-white hover:text-sky-500" to="/">Inicio</NuxtLink>
          </li>
          <li>
            <NuxtLink class="text-white hover:text-sky-500" to="/about">Acerca de</NuxtLink>
          </li>
          <li>
            <NuxtLink class="text-white hover:text-sky-500" to="/productos">Productos</NuxtLink>
          </li>
        </ul>
      </nav>
    </header>
    <div class="container mx-auto p-4">
      <slot />
    </div>
  </div>
</template>
<style scoped>
.router-link-exact-active {
  font-weight: bolder;
}
</style>
 | 
- Modificar 
layouts/productos-layout.vue 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  | <template>
  <div>
    <header class="shadow-sm bg-sky-900">
      <nav class="container mx-auto p-4 flex items-center justify-between">
        <NuxtLink to="/productos">
          <img class="w-auto h-10" src="~/assets/images/logo.png" />
        </NuxtLink>
        <ul class="flex gap-4">
          <li>
            <NuxtLink class="text-white hover:text-sky-500" to="/">Inicio</NuxtLink>
          </li>
          <li>
            <NuxtLink class="text-white hover:text-sky-500" to="/about">Acerca de</NuxtLink>
          </li>
          <li>
            <NuxtLink class="text-white hover:text-sky-500" to="/productos">Productos</NuxtLink>
          </li>
        </ul>
      </nav>
    </header>
    <div class="container mx-auto p-4">
      <slot />
    </div>
    <footer class="container mx-auto p-4 flex justify-between border-t-2 border-orange-900">
      <ul class="flex gap-4">
        <li><NuxtLink to="/">Inicio</NuxtLink></li>
        <li><NuxtLink to="/about">Acerca de</NuxtLink></li>
        <li><NuxtLink to="/productos">Productos</NuxtLink></li>
      </ul>
    </footer>
  </div>
</template>
<style scoped>
.router-link-exact-active {
  font-weight: bolder;
}
</style>
 | 
- Extender el alcance de TailwindCSS, crear 
assets/css/taildwind.css  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  | @tailwind base;
@tailwind components;
@tailwind utilities;
body {
    @apply bg-gray-200;
}
@layer components {
    .btn {
        @apply bg-sky-900 text-white px-3 py-2 rounded-md text-sm hover:font-bold;
    }
}
 | 
Fetch data
1
2
3
4
  | fetch('https://fakestoreapi.com/products')
            .then(res=>res.json())
            .then(json=>console.log(json))
 | 
1
2
3
  | fetch('https://fakestoreapi.com/products/1')
            .then(res=>res.json())
            .then(json=>console.log(json))
 | 
- Modificar 
productos/index.vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  | <template>
  <div>
    <h2>Productos</h2>
    <div class="grid grid-cols-4 gap-5">
      <div v-for="producto in productos">
        <NuxtLink :to="`/productos/${producto.id}`">{ { producto.title } }</NuxtLink>
      </div>
    </div>
  </div>
</template>
<script setup>
definePageMeta({
  layout: "productos-layout",
})
// fetch los productos de la api
const { data : productos } = await useFetch('https://fakestoreapi.com/products')
</script>
<style scoped>
h2 {
  margin-bottom: 20px;
  font-size: 36px;
}
p {
  margin: 20px 0;
}
</style>
 | 
- Modificar 
productos/[id_producto].vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  | <template>
  <div>
    <p></p>
    <p></p>
    <p></p>
  </div>
</template>
<script setup>
const { id_producto } = useRoute().params
const uri = 'https://fakestoreapi.com/products/' + id_producto
//fetch el producto
const { data: producto } = await useFetch(uri, { key: id_producto } )
definePageMeta({
  layout: "productos-layout",
})
</script>
<style scoped>
</style>
 | 
Componentes reusables
El directorio components/ es donde coloca todos sus componentes de Vue que luego se pueden importar dentro de sus páginas u otros componentes. Nuxt importa automáticamente cualquier componente en su directorio components/ (junto con los componentes que están registrados por cualquier módulo que pueda estar usando).
- Crear 
components/ProductoCard.vue 
1
2
3
4
5
6
7
8
9
10
11
  | <template>
    <div>
    </div>
</template>
<script setup>
</script>
<style scoped>
</style>
 | 
- Modificar 
productos/index.vue 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  | <template>
  <div>
    <h2>Productos</h2>
    <div class="grid grid-cols-4 gap-5">
      <div v-for="producto in productos">
        <ProductoCard :p="producto"/>
      </div>
    </div>
  </div>
</template>
<script setup>
definePageMeta({
  layout: "productos-layout",
})
// fetch los productos de la api
const { data : productos } = await useFetch('https://fakestoreapi.com/products')
</script>
<style scoped>
h2 {
  margin-bottom: 20px;
  font-size: 36px;
}
p {
  margin: 20px 0;
}
</style>
 | 
- Modificar 
assets/css/tailwind.css 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  | @tailwind base;
@tailwind components;
@tailwind utilities;
body {
    @apply bg-gray-200;
}
@layer components {
    .btn {
        @apply bg-sky-900 text-white px-3 py-2 rounded-md text-sm hover:font-bold;
    }
    .card {
        @apply p-3 rounded-md bg-white shadow-md h-full;
    }
}
 | 
- Modificar 
components/ProductoCard.vue 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  | <template>
    <div class="card text-center">
        <img :src="p.image" alt="imagen del producto" class="object-scale-down h-48 w-96">
        <p class="font-bold text-gra-500 m4 truncate">{ { p.title } }</p>
        <NuxtLink :to="`/productos/${p.id}`">
            <p class="btn my-4">Ver Detalles</p>
        </NuxtLink>
    </div>
</template>
<script setup>
    const { p } = defineProps(['p'])
</script>
<style scoped>
</style>
 | 
- Crear 
components/ProductoDetalles.vue 
1
2
3
4
5
6
7
8
9
10
11
  | !<template>
    <div>
    </div>
</template>
<script setup>
</script>
<style scoped>
</style>
 | 
- Modificar 
productos/[id_producto].vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  | <template>
  <div>
    <ProductoDetalles :p = "producto"/>
  </div>
</template>
<script setup>
const { id_producto } = useRoute().params
const uri = 'https://fakestoreapi.com/products/' + id_producto
//fetch el producto
const { data: producto } = await useFetch(uri, { key: id_producto } )
definePageMeta({
  layout: "productos-layout",
})
</script>
<style scoped>
</style>
 | 
- Modificar 
components/ProductoDetalles.vue 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  | <template>
    <div class="card">
        <div class="grid grid-cols-2 gap-10">
            <div class="p-7">
                <img :src="p.image" alt="imagen del producto" class="mx-auto my-7">
            </div>
            <div class="p-7">
                <h1 class="text-4xl my-7">{ { p.title } }</h1>
                <p class="text-xl my-7">Precio - ${ { p.price } }</p>
                <h3 class="font-bold border-b-2 mb-4 pb-2">Descripción del producto:</h3>
                <p class="mb-7">{ {p.description} }</p>
            </div>
        </div>
    </div>
</template>
<script setup>
    const { p } = defineProps(['p'])
</script>
<style scoped>
    img {
        max-width: 400px;
    }
</style>
 | 
Pagina de error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  | <template>
    <div class="mt-7 max-w-sm mx-auto text-center card">
        <p class="mt-7 text-7xl text-red-700 font-bold">{ { error.statusCode } }</p>
        <p class="mt-7 text-3xl">Lo sentimos algo salio mal =( </p>
        <p class="mt-7">{ { error.message } }</p>
        <button class="btn my-7" @click="handleError">Volver al inicio...</button>
    </div>
</template>
<script setup>
    defineProps(['error'])
    const handleError = (
) => clearError({ redirect: '/' })
</script>
<style scoped>
</style>
 | 
- Modificar 
productos/[id_producto].vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  | <template>
  <div>
    <ProductoDetalles :p = "producto"/>
  </div>
</template>
<script setup>
const { id_producto } = useRoute().params
const uri = 'https://fakestoreapi.com/products/' + id_producto
//fetch el producto
const { data: producto } = await useFetch(uri, { key: id_producto } )
//Crear mensaje de error personalizado
if(!producto.value) {
  throw createError({ statusCode: 404, statusMessage: 'Producto no encontrado...', fatal: true })
}
definePageMeta({
  layout: "productos-layout",
})
</script>
<style scoped>
</style>
 | 
- Crear directorio 
public y copiar favicon.png a esa ubicacion - modificar 
nuxt.config.ts  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  | // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
    modules: ['@nuxtjs/tailwindcss'],
    app: {
        head: {
            title: 'Tienda UNIVO',
            meta: [
                { name: 'description', content: 'Tienda de mercaderia UNIVO' }
            ],
            link: [
                { rel: 'icon', type: 'image/png', href: 'icon_100x100.png' },
                { rel: 'stylesheet', href: 'https://fonts.googleapis.com/icon?family=Material+Icons' }
            ]
        }
    }
})
 | 
- Modificar 
productos/Producto.detalle.vue 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  | <template>
    <div class="card">
        <div class="grid grid-cols-2 gap-10">
            <div class="P-7">
                <img :src="p.image" alt="imagen del producto" class="mx-auto my-7">
            </div>
            <div class="p-7">
                <h1 class="text-4xl my-7"></h1>
                <p class="text-xl my-7">Precio - $</p>
                <h3 class="font-bold border-b-2 mb-4 pb-2 border-orange-900">Descripcion del producto:</h3>
                <p class="mb-7"></p>
                <button class="btn flex"><i class="material-icons mr-2">add_shopping_cart</i> Agregar al carrito</button>
            </div>
        </div>
    </div>
</template>
<script setup>
const { p } = defineProps(['p'])
</script>
<style scoped>
img {
    max-width: 400px;
}
</style>
 | 
- Modificar 
productos/index.vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  | <template>
  <div>
    <h2>Productos</h2>
    <div class="grid grid-cols-4 gap-5">
      <div v-for="producto in productos">
        <ProductoCard :p="producto" />
      </div>
    </div>
  </div>
</template>
<script setup>
useHead({
  title: 'Tienda UNIVO | Productos',
  meta: [
    { name: 'description', content: 'Listado de productos UNIVO' }
  ],
})
definePageMeta({
  layout: "productos-layout",
})
// fetch los productos de la api
const { data: productos } = await useFetch('https://fakestoreapi.com/products')
</script>
<style scoped>
h2 {
  margin-bottom: 20px;
  font-size: 36px;
}
p {
  margin: 20px 0;
}
</style>
 | 
- Modificar 
productos/[id_producto].vue  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  | <template>
  <div>
    <Head>
      <Title>Tienda UNIVO | { { producto.title } }</Title>
      <Meta name="description" :content="producto.description"/>
    </Head>
    <ProductoDetalles :p = "producto"/>
  </div>
</template>
<script setup>
const { id_producto } = useRoute().params
const uri = 'https://fakestoreapi.com/products/' + id_producto
//fetch el producto
const { data: producto } = await useFetch(uri, { key: id_producto } )
//Crear mensaje de error personalizado
if(!producto.value) {
  throw createError({ statusCode: 404, statusMessage: 'Producto no encontrado...', fatal: true })
}
definePageMeta({
  layout: "productos-layout",
})
</script>
<style scoped>
</style>
 |