Inicio NuxtJS
Post
Cancel

NuxtJS

NuxtJSNuxtJS

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:

  1. Marco de JavaScript: Un marco de JavaScript para brindar reactividad y componentes web, elegimos Vue.js.
  2. 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.
  3. Última sintaxis de JavaScript: Un transpilador para escribir la última sintaxis de JavaScript mientras se admite navegadores heredados, usamos esbuild para eso.
  4. 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.
  5. 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
1
npm install
  • 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
npm run dev
  • Modificar app.vue
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>

  • Crear pages/about.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>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>

  • Modificar page/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
<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

  • Instalar la dependencia
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))

  • Obtener solo un producto
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

  • Crear error.vue
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>

Metadata y cabeceras

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

This post is licensed under CC BY 4.0 by the author.