Conceptos básicos de Next.js 14

1

¿Qué es Next.js y por qué aprenderlo si quieres ser frontend senior?

2

Arquitectura de un proyecto de Next.js

3

Herramientas y stack utilizado en el curso

4

Cómo crear rutas en Next.js

5

Cómo crear Layout en Next.js

6

Cómo funciona la navegación en Next.js

7

Manejo de parámetros en rutas en Next.js

8

React Server Components en Next.js: notación "use Client"

9

Creación de arquitectura de landing page en Next.js

Quiz: Conceptos básicos de Next.js 14

Manejo de estilos y estáticos en Next.js 14

10

CSS Modules en Next.js 13

11

Uso de Sass en Next.js

12

Cómo utilizar estilos globales en Next.js

13

Cómo agregar archivos estáticos en Next.js

14

Manejo y optimización de imágenes con Next Image

15

Optimización del componente image en Next.js

16

Optimización de fuentes con Next.js

17

Creando estilos dinámicos aplicando condicionales en Next.js

Quiz: Manejo de estilos y estáticos en Next.js 14

Data Fetching en Next.js

18

Creación de tienda de Shopify para un proyecto en Next.js

19

Manejo de variables de entorno en Next.js

20

Cómo obtener información de una API con Next.js

21

Manejo de estado de carga con el archivo loading.tsx

22

Route Grouping en Next.js

23

Manejo de errores en la UI con el archivo error.tsx

Quiz: Data Fetching en Next.js

Next.js Avanzado

24

Implementando páginas de Not Found y error global

25

Cómo impactan los React Server Components en un proyecto en Next.js

26

Cuándo utilizar layout o template en Next.js

27

Arquitectura profesional para data fetching en un proyecto en Next.js

28

Next.js para backend: manejando rutas con archivos Route Handlers

29

Proyecto: implementando la página de tienda

30

Patrones de data fetching en Next.js

31

Proyecto: filtrando categorías de productos

32

Data fetching de parámetros en el servidor y cliente

33

Proyecto: página de producto y arreglos en el sitio

34

Cómo funciona el Fetch y el Caché de Next.js

35

Revalidando cache con revalidateTag y revalidatePath en Next.js

36

Cómo hacer redirects en Next.js

37

Proyecto: HTML dinámico en la descripción del producto

38

Mejorando SEO de una página en Next.js

Quiz: Next.js Avanzado

Autenticación y autorización

39

Manejando autenticación y autorización con Storefront APÏ de Shopify

40

Server Actions en Next.js

41

Proyecto: proceso de Sign-Up con GraphQL

42

Manejo de cookies para colocar un token de acceso de un proyecto en Next.js

43

Cómo implementar un flujo de login en un proyecto en Next.js

44

Validando token de acceso de usuario en un proyecto en Next.js

45

Proyecto: implementando el carrito de compras

46

Manejo de estado global con zustand en Next.js

47

Proyecto: agregando items al carrito de compras

48

Cómo integrar el checkout de Shopify a un proyecto en Next.js

49

Implementar middleware en proyecto en Next.js para protección de rutas

Inteligencia Artificial

50

Creando componente de chatbot de ventas con la SDK IA de Vercel

51

Implementación de un bot de ventas en una app Next.js

Performance

52

Optimización de carga con parallel routing en Next.js

53

Análisis de bundle para Next.js 14

Quiz: Performance

Frontend Ops

54

Edge runtime

55

Despliegue de un proyecto Next.js en Vercel

56

Mejores prácticas en arquitecturas empresariales

Quiz: Frontend Ops

Next.js es parte de tu nuevo stack

57

¡Has creado un proyecto en Next.js!

No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Convierte tus certificados en títulos universitarios en USA

Antes: $249

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

17 Días
0 Hrs
35 Min
8 Seg
Curso de Next.js 14

Curso de Next.js 14

Enrique Devars

Enrique Devars

Manejo de estado de carga con el archivo loading.tsx

21/57
Recursos

Aportes 13

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Hay otra forma de aplicar el loading sin tener que pasar los componentes que son estaticos al layout. . Personalmente no me gusta tanto la idea de pasar esos componentes al layout porque el layout deberia usarse para un diseño transversal entre diferentes paginas, es decir, deberian ir en el layout componentes que requieren varias paginas. . Esa es mi opinion, es correcto también hacerlo como en el curso. Pero ya he usado un poco de next y les voy a mostrar otra forma de hacerlo que a mi criterio es más optima. (Es probable que más adelante en el curso enseñen esta otra forma) . La cosa es la siguiente: El loading file va a ser el skeleton de la pagina entera. Pero dentro de la pagina es probable que no todos los componentes necesiten esperar a que la data llegue desde una api, sino que hay componentes estaticos que están disponibles desde el primer momento como un h1, h2, estilos, interfaz y basicamente todo lo que no sea traer datos de una api la base de datos. . Siguiendo al estrategia del archivo loading, esos componentes no se van a mostrar tampoco hasta que el componente que requiere traer datos asincronamente no esté listo. . Ese es un problema porque el usuario podria ir interactuando con los componentes estaticos mientras esperamos a que el componente que está haciendo algo asincronamente, esté listo. . Lo que podemos hacer es en lugar de usar el archivo loading, ser más precisos y solo mostrar un skeleton en aquellos componentes que requiren una operación asincrona. . Eso se hace envolviendo esos componentes dentro de un Reaact Suspense (es un componente de React) que recibe la prop "fallback". Dicho fallback es el skeleton que se mostrará mientras se espera a que la data esté lista. Pero los demás componentes que no estén envueltos dentro del \<Suspense> se mostrarán inmediatamente y el usuario puede interactuar con ellos. . Así quedaría: . ![](file:///C:/Users/Usuario/Downloads/before.png)![](file:///C:/Users/Usuario/Downloads/before.png)![](https://static.platzi.com/media/user_upload/before-ab279c12-25c5-49ed-89b3-d4c7b0d88b57.jpg). Este es un proyecto personal donde estoy usando next 14. Como puedes ver, el componente principal es la pagina home, dentro hay otros 3 componentes principales de los cuales dos estan envueltos en un suspense. . Y así es como se mostraría: . ![](https://static.platzi.com/media/user_upload/result-edd33e4c-e6ee-48cf-bb48-e9ddf116dd7b.jpg). La pagina se muestra inmediantamente con los componentes estaticos mientras los otros cargan

Sobre la solución, me pareció muy útil la propia documentación de nextjs para entender mejor:

Con pasar los componentes estáticos al layout.tsx, los mismos serán renderizados directamente y el loader aplicará únicamente al componente page.tsx (y sus hijos) que esté en ese mismo directorio/ruta.

para que no nos salga el error en el mapeo de productos podemos crear una interface con todos los tipos que tiene el objeto producto les facilito el codigo ```js interface Product { id: number; title: string; body_html: string; vendor: string; product_type: string; created_at: string; handle: string; updated_at: string; published_at: string; template_suffix: null | string; published_scope: string; tags: string; status: string; admin_graphql_api_id: string; variants: Array<{ /* Detalles de la variante */ }>; // Necesitarías definir el tipo para las variantes options: Array<{ /* Detalles de las opciones */ }>; // Necesitarías definir el tipo para las opciones images: Array<{ /* Detalles de las imágenes */ }>; // Necesitarías definir el tipo para las imágenes image: { id: number; alt: null | string; position: number; product_id: number; created_at: string; updated_at: string; admin_graphql_api_id: string; width: number; height: number; src: string; variant_ids: number[]; }; } ``` dentro de images, options y variant tenemos tambien otras interfaces y aplicando este params el codigo quedaria asi ```ts const getProducts = async (): Promise<Product[]> => { // ... (código de getProducts) }; export const MainProduct: React.FC = () => { const [products, setProducts] = useState<Product[]>([]); useEffect(() => { const fetchProducts = async () => { const productsData = await getProducts(); setProducts(productsData); }; fetchProducts(); }, []); // El segundo argumento [] asegura que useEffect solo se ejecute una vez al montar el componente return ( <section className={styles.MainProducts}>

✨ New products released!

{products.map((product) => ( <article key={product.id}>

{product.title}

<Image src={product.images[0].src} fill alt={product.title} loading="eager" /> </article> ))}
</section> ); }; ```
No me gusto el curso, este debiera enfocarse en Next.js y no en implementar y resolver los problemas de Shopify con este framework. Evidentemente hay una falla metodologica. Ni siquiera es util para quienes quieran implementar Shopify con Next. Es decir, no ayuda ni para Next.js ni para Shopify. Hay cursos en platzi que se crean una API para proveer datos, manteniendo simplicidad en los contenidos. Aca se complico mas de lo necesario es desmedro de la calidad del curso. . El instructor tiene experiencia profesional, que no se ve reflejado en el curso por falta de prolijidad.
yo estoy tomando este curso como base y estoy utilizando la documentación de Nextjs y la api de Platzi, jajajjaja
No entendi nada
```````tsx "use client"; import Image from "next/image"; import { useEffect, useState } from "react"; export interface Products { id: number; title: string; price: number; description: string; images: string[]; creationAt: Date; updatedAt: Date; category: Category; } export interface Category { id: number; name: string; image: string; creationAt: Date; updatedAt: Date; } const getProducts = async (): Promise<Products[]> => { const response = await fetch("https://api.escuelajs.co/api/v1/products"); const data = await response.json(); return data; }; export function MainProdruts() { // const products = await getProducts(); // console.log(products); const [products, setProducts] = useState<Products[]>([]); useEffect(() => { const fetchProducts = async () => { const data = await getProducts(); setProducts(data); }; fetchProducts(); }, []); console.log(products); return ( <section>

✨ New products released!

{products?.map((product) => { return ( <article className="relative z-1 min-h-[400px] min-w-[500px]" key={product.id} >

{product.title}

<Image className="opacity-40 object-cover" src={product.images[1]} fill alt={product.title} loading="eager" /> </article> ); })}
</section> ); } ``````js ```````
sa```js aa ```
```js // Generated by https://quicktype.io export interface GetProductsResponse { products: Product[]; } export interface Product { id: number; title: string; body_html: string; vendor: Vendor; product_type: string; created_at: string; handle: string; updated_at: string; published_at: string; template_suffix: null; published_scope: PublishedScope; tags: string; status: Status; admin_graphql_api_id: string; variants: Variant[]; options: Option[]; images: Image[]; image: Image; } export interface Image { id: number; alt: null; position: number; product_id: number; created_at: string; updated_at: string; admin_graphql_api_id: string; width: number; height: number; src: string; variant_ids: any[]; } export interface Option { id: number; product_id: number; name: Name; position: number; values: Option1[]; } export enum Name { Title = "Title", } export enum Option1 { DefaultTitle = "Default Title", } export enum PublishedScope { Global = "global", } export enum Status { Active = "active", } export interface Variant { id: number; product_id: number; title: Option1; price: string; sku: null; position: number; inventory_policy: InventoryPolicy; compare_at_price: null | string; fulfillment_service: FulfillmentService; inventory_management: null; option1: Option1; option2: null; option3: null; created_at: string; updated_at: string; taxable: boolean; barcode: null; grams: number; image_id: null; weight: number; weight_unit: WeightUnit; inventory_item_id: number; inventory_quantity: number; old_inventory_quantity: number; requires_shipping: boolean; admin_graphql_api_id: string; } export enum FulfillmentService { Manual = "manual", } export enum InventoryPolicy { Deny = "deny", } export enum WeightUnit { Kg = "kg", } export enum Vendor { FutureWorld = "Future World", } ```*// Generated by* [*https://quicktype.io*](https://quicktype.io) export interface GetProductsResponse {  products: Product\[];} export interface Product {  id:                   number;  title:                string;  body\_html:            string;  vendor:               Vendor;  product\_type:         string;  created\_at:           string;  handle:               string;  updated\_at:           string;  published\_at:         string;  template\_suffix:      null;  published\_scope:      PublishedScope;  tags:                 string;  status:               Status;  admin\_graphql\_api\_id: string;  variants:             Variant\[];  options:              Option\[];  images:               Image\[];  image:                Image;} export interface Image {  id:                   number;  alt:                  null;  position:             number;  product\_id:           number;  created\_at:           string;  updated\_at:           string;  admin\_graphql\_api\_id: string;  width:                number;  height:               number;  src:                  string;  variant\_ids:          any\[];} export interface Option {  id:         number;  product\_id: number;  name:       Name;  position:   number;  values:     Option1\[];} export enum Name {  Title = "Title",} export enum Option1 {  DefaultTitle = "Default Title",} export enum PublishedScope {  Global = "global",} export enum Status {  Active = "active",} export interface Variant {  id:                     number;  product\_id:             number;  title:                  Option1;  price:                  string;  sku:                    null;  position:               number;  inventory\_policy:       InventoryPolicy;  compare\_at\_price:       null | string;  fulfillment\_service:    FulfillmentService;  inventory\_management:   null;  option1:                Option1;  option2:                null;  option3:                null;  created\_at:             string;  updated\_at:             string;  taxable:                boolean;  barcode:                null;  grams:                  number;  image\_id:               null;  weight:                 number;  weight\_unit:            WeightUnit;  inventory\_item\_id:      number;  inventory\_quantity:     number;  old\_inventory\_quantity: number;  requires\_shipping:      boolean;  admin\_graphql\_api\_id:   string;} export enum FulfillmentService {  Manual = "manual",} export enum InventoryPolicy {  Deny = "deny",} export enum WeightUnit {  Kg = "kg",} export enum Vendor {  FutureWorld = "Future World",}
```js export const MainProducts = async () => { const products = await getProducts(); return( <section className={styles.MainProducts}>

✨ New products released!

{products?.map((product: any) => { const imageSrc = product.images[0].src; return ( <article key={product.id}>

{product.title}

<Image src={imageSrc} fill alt={product.title} loading="eager" /> </article> ) })}
</section> ) } ```export const MainProducts = async () => {  const products = await getProducts();  return(    \<section className={styles.MainProducts}>      \

✨ New products released!\

      \
        {products?.map((product: any) => {          const imageSrc = product.images\[0].src;          return (            \<article key={product.id}>              \

{product.title}\

              \<Image src={imageSrc} fill alt={product.title} loading="eager" />            \</article>          )        })}      \
    \</section>  )}
El tipado de lo que arroja la API sería el siguiente: `// src/models/interface/products.ts` `export interface Product {` ` admin_graphql_api_id: string;` ` body_html: string;` ` created_at: Date;` ` handle: string;` ` id: number;` ` image: Image;` ` images: Image[];` ` options: Option[];` ` product_type: string;` ` published_at: Date;` ` published_scope: string;` ` status: string;` ` tags: string;` ` template_suffix: null;` ` title: string;` ` updated_at: Date;` ` variants: Variant[];` ` vendor: string;` `}` `export interface Image {` ` admin_graphql_api_id: string;` ` alt: null;` ` created_at: Date;` ` height: number;` ` id: number;` ` position: number;` ` product_id: number;` ` src: string;` ` updated_at: Date;` ` variant_ids: any[];` ` width: number;` `}` `export interface Option {` ` id: number;` ` name: string;` ` position: number;` ` product_id: number;` ` values: string[];` `}` `export interface Variant {` ` admin_graphql_api_id: string;` ` barcode: null;` ` compare_at_price: null | string;` ` created_at: Date;` ` fulfillment_service: string;` ` grams: number;` ` id: number;` ` image_id: null;` ` inventory_item_id: number;` ` inventory_management: null;` ` inventory_policy: string;` ` inventory_quantity: number;` ` old_inventory_quantity: number;` ` option1: string;` ` option2: null;` ` option3: null;` ` position: number;` ` price: string;` ` product_id: number;` ` requires_shipping: boolean;` ` sku: null;` ` taxable: boolean;` ` title: string;` ` updated_at: Date;` ` weight: number;` ` weight_unit: string;` `}`
O simplemente para no hacer todo eso (que a mi parecer seria lo correcto) podriamos poner a product de tipo any asi se va el error ```ts
{products?.map((product: any) => { const imageSrc = product.images[0].src; return ( <article key={product.id}>

{product.title}

<Image src={imageSrc} fill alt={product.title} loading="eager" /> </article> ); })}
```
Yo tipe completamente los elementos del producto usando RapidApi `export interface ProductsList {  products: Product[];}` `export interface Product {  admin_graphql_api_id: string;  body_html: string;  created_at: Date;  handle: string;  id: number;  image: Image;  images: Image[];  options: Option[];  product_type: string;  published_at: Date;  published_scope: PublishedScope;  status: Status;  tags: string;  template_suffix: null;  title: string;  updated_at: Date;  variants: Variant[];  vendor: Vendor;}` `export interface Image {  admin_graphql_api_id: string;  alt: null;  created_at: Date;  height: number;  id: number;  position: number;  product_id: number;  src: string;  updated_at: Date;  variant_ids: any[];  width: number;}` `export interface Option {  id: number;  name: Name;  position: number;  product_id: number;  values: Option1[];}` `export enum Name {  Title = "Title",}` `export enum Option1 {  DefaultTitle = "Default Title",}` `export enum PublishedScope {  Global = "global",}` `export enum Status {  Active = "active",}` `export interface Variant {  admin_graphql_api_id: string;  barcode: null;  compare_at_price: null | string;  created_at: Date;  fulfillment_service: FulfillmentService;  grams: number;  id: number;  image_id: null;  inventory_item_id: number;  inventory_management: null;  inventory_policy: InventoryPolicy;  inventory_quantity: number;  old_inventory_quantity: number;  option1: Option1;  option2: null;  option3: null;  position: number;  price: string;  product_id: number;  requires_shipping: boolean;  sku: null;  taxable: boolean;  title: Option1;  updated_at: Date;  weight: number;  weight_unit: WeightUnit;}` `export enum FulfillmentService {  Manual = "manual",}` `export enum InventoryPolicy {  Deny = "deny",}` `export enum WeightUnit {  Kg = "kg",}` `export enum Vendor {  FutureWorld = "Future World",}`