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>
|