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

Curso de Next.js 14

Curso de Next.js 14

Enrique Devars

Enrique Devars

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

20/57
Recursos

Aportes 17

Preguntas 4

Ordenar por:

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

Usando la extensión para VS Code llamada RapidApi, hice una prueba de si la api de shopify funciona: ![](https://static.platzi.com/media/user_upload/image-f00e1c21-de8d-4960-818e-08e6b0bda7cb.jpg) Además que me permitió crear una interfaz para los datos de la api y así manipular más fácilmente los datos de los productos ![](https://static.platzi.com/media/user_upload/image-8c30a974-eb23-433e-8203-47840e6da5dc.jpg)

Bueno, tuve varios errores, pero lo logré corregir. Primero, si coloco las variables de entorno en .env no me lo leía, entonces tuve que crear .env.local.

Luego, en .env.local no puedes colocar el https://

Y luego, en MainProducts, sí debes colocar el https://

si les ocurre este error al hacer fetching de datos: ![](https://static.platzi.com/media/user_upload/image-ecf09060-d827-44c2-8315-89d8116e34d5.jpg) Agregenle al fetch https:// ![](https://static.platzi.com/media/user_upload/image-9d88edb9-3924-4c8d-a012-a853bd9bbefa.jpg) Y listo
Tips para esta clase: 1. la url de tu tienda la puedes ver desde admin.shopify en configuraciones, arriba izquierda ![](https://static.platzi.com/media/user_upload/Screenshot%202024-05-21%20at%203.37.17%E2%80%AFp.m.-60ce000d-e81b-4c74-9ccd-3d5e391a535e.jpg) 1. No olvides poner el https:// en el host de tu tienda en los .env 2. recuerda que el componente page, padre en la landing page, pusiste un "use client", deberas quitarlo para que funcione
Logré solucionarlo, tuve que especificar que el método era un GET. Quedó así: ``const getProducts = async () => { const response = await fetch(`https://${process.env.SHOPIFY_HOSTNAME}/admin/api/2023-10/products.json`, { method: "GET", headers: new Headers({ "X-Shopify-Access-Token": process.env.SHOPIFY_API_KEY as string, }), });`` ` const data = await response.json();` ` return data;};`
En mi caso la versión del API de Shopify es `2024-01` y el ID de la colección (la que consume la home page) era diferente, así que creé 2 variables de entorno adicionales para esto, quedándome el `.env` así: `SHOPIFY_HOSTNAME="HOST_SHOPIFY"` `SHOPIFY_API_KEY="API_KEY_SHOPIFY"` `CACHE_TOKEN=""` `WEBHOOK_VERSION='API_VERSION'` `HOME_PAGE_COLLECTION='HOME_PAGE_COLLECTION' ``# Provided at Shopify store`
![](https://static.platzi.com/media/user_upload/image-f68b2541-0fa3-4b4c-884b-d9251584b571.jpg)![]()
1:17 se te olvidó censurar ese correo jajaja
Dejo por acá la interfaz para mapear la respuesta del endpoint de productos ```js export interface Products { products: Product[]; } export interface Product { id: number; title: string; body_html: string; vendor: Vendor; product_type: string; created_at: Date; handle: string; updated_at: Date; published_at: Date; 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: Date; updated_at: Date; 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: Date; updated_at: Date; taxable: boolean; barcode: null; grams: number; weight: number; weight_unit: WeightUnit; inventory_item_id: number; inventory_quantity: number; old_inventory_quantity: number; requires_shipping: boolean; admin_graphql_api_id: string; image_id: null; } export enum FulfillmentService { Manual = "manual", } export enum InventoryPolicy { Deny = "deny", } export enum WeightUnit { Kg = "kg", } export enum Vendor { FutureWorld = "Future World", } ``` La petición **HTTP** quedaría de la siguiente manera: ```js const getProducts = async (): Promise<Product[]> => { const response: Products = await fetch( `${process.env.SHOPIFY_HOSTNAME}/admin/api/2023-10/products.json`, { headers: { "X-Shopify-Access-Token": process.env.SHOPIFY_API_KEY!, }, } ).then((resp) => resp.json()); return response.products; }; ```Se debe configurar los remote image patterns en el archivo next.config.mjs: ```js const nextConfig = { sassOptions: { includePaths: [path.join(__dirname, 'src/sass')], prependData: `@import "main.sass"`, }, images: { remotePatterns: [ { protocol: 'https', hostname: ' cdn.shopify.com', pathname: '/s/files/1/0888/5816/5540/files/*', }, ] } }; ```const nextConfig = {  sassOptions: {    includePaths: \[path.join(\_\_dirname, 'src/sass')],    prependData: `@import "main.sass"`,  },  images: {    remotePatterns: \[      {        protocol: 'https',        hostname: ' cdn.shopify.com',        pathname: '/s/files/1/0888/5816/5540/files/\*',    },    ]  }};
Dejo por acá la interfaz para mapear la respuesta del endpoint de productosexport interface *Products* {  products*: Product\[]*;} export interface *Product* {  id*: number*;  title*: string*;  body\_html*: string*;  vendor*: Vendor*;  product\_type*: string*;  created\_at*: Date*;  handle*: string*;  updated\_at*: Date*;  published\_at*: Date*;  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*: Date*;  updated\_at*: Date*;  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*: Date*;  updated\_at*: Date*;  taxable*: boolean*;  barcode*: null*;  grams*: number*;  weight*: number*;  weight\_unit*: WeightUnit*;  inventory\_item\_id*: number*;  inventory\_quantity*: number*;  old\_inventory\_quantity*: number*;  requires\_shipping*: boolean*;  admin\_graphql\_api\_id*: string*;  image\_id*: null*;} export enum FulfillmentService {  Manual = "manual",} export enum InventoryPolicy {  Deny = "deny",} export enum WeightUnit {  Kg = "kg",} export enum Vendor {  FutureWorld = "Future World",} ```js export interface Products { products: Product[]; } export interface Product { id: number; title: string; body_html: string; vendor: Vendor; product_type: string; created_at: Date; handle: string; updated_at: Date; published_at: Date; 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: Date; updated_at: Date; 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: Date; updated_at: Date; taxable: boolean; barcode: null; grams: number; weight: number; weight_unit: WeightUnit; inventory_item_id: number; inventory_quantity: number; old_inventory_quantity: number; requires_shipping: boolean; admin_graphql_api_id: string; image_id: null; } export enum FulfillmentService { Manual = "manual", } export enum InventoryPolicy { Deny = "deny", } export enum WeightUnit { Kg = "kg", } export enum Vendor { FutureWorld = "Future World", } ```La función para hacer la petición **HTTP** quedaría de la siguiente manera: ```js const getProducts = async (): Promise<Product[]> => { const response: Products = await fetch( `${process.env.SHOPIFY_HOSTNAME}/admin/api/2023-10/products.json`, { headers: { "X-Shopify-Access-Token": process.env.SHOPIFY_API_KEY!, }, } ).then((resp) => resp.json()); return response.products; }; ```const getProducts = async *(): Promise\<Product\[]>* => {  const response*: Products* = await fetch(    `${process.env.SHOPIFY\_HOSTNAME}/admin/api/2023-10/products.json`,    {      headers: {        "X-Shopify-Access-Token": process.env.SHOPIFY\_API\_KEY!,      },    }  ).then(*(resp)* => resp.json());   return response.products;}; Se debe configurar los remote Image patterns en el archivo next.config.mjs:const nextConfig = {  sassOptions: {    includePaths: \[path.join(\_\_dirname, 'src/sass')],    prependData: `@import "main.sass"`,  },  images: {    remotePatterns: \[      {        protocol: 'https',        hostname: ' cdn.shopify.com',        pathname: '/s/files/1/0888/5816/5540/files/\*',    },    ]  }}; ```js const nextConfig = { sassOptions: { includePaths: [path.join(__dirname, 'src/sass')], prependData: `@import "main.sass"`, }, images: { remotePatterns: [ { protocol: 'https', hostname: ' cdn.shopify.com', pathname: '/s/files/1/0888/5816/5540/files/*', }, ] } }; ```
Dejo por acá la interfaz para mapear la respuesta del endpoint de productos ```js export interface Products { products: Product[]; } export interface Product { id: number; title: string; body_html: string; vendor: Vendor; product_type: string; created_at: Date; handle: string; updated_at: Date; published_at: Date; 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: Date; updated_at: Date; 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: Date; updated_at: Date; taxable: boolean; barcode: null; grams: number; weight: number; weight_unit: WeightUnit; inventory_item_id: number; inventory_quantity: number; old_inventory_quantity: number; requires_shipping: boolean; admin_graphql_api_id: string; image_id: null; } export enum FulfillmentService { Manual = "manual", } export enum InventoryPolicy { Deny = "deny", } export enum WeightUnit { Kg = "kg", } export enum Vendor { FutureWorld = "Future World", } ```La función para hacer la petición HTTP quedaria de la siguiente manera: ```js const getProducts = async (): Promise<Product[]> => { const response: Products = await fetch( `${process.env.SHOPIFY_HOSTNAME}/admin/api/2023-10/products.json`, { headers: { "X-Shopify-Access-Token": process.env.SHOPIFY_API_KEY!, }, } ).then((resp) => resp.json()); return response.products; }; ```Se debe configurar en el archivo next.config.mjs los remote images ```js const nextConfig = { sassOptions: { includePaths: [path.join(__dirname, 'src/sass')], prependData: `@import "main.sass"`, }, images: { remotePatterns: [ { protocol: 'https', hostname: ' cdn.shopify.com', pathname: '/s/files/1/0888/5816/5540/files/*', }, ] } }; ```
Dejo por acá la interfaz para mapear la respuesta del endpoint de productos ```js export interface Products { products: Product[]; } export interface Product { id: number; title: string; body_html: string; vendor: Vendor; product_type: string; created_at: Date; handle: string; updated_at: Date; published_at: Date; 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: Date; updated_at: Date; 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: Date; updated_at: Date; taxable: boolean; barcode: null; grams: number; weight: number; weight_unit: WeightUnit; inventory_item_id: number; inventory_quantity: number; old_inventory_quantity: number; requires_shipping: boolean; admin_graphql_api_id: string; image_id: null; } export enum FulfillmentService { Manual = "manual", } export enum InventoryPolicy { Deny = "deny", } export enum WeightUnit { Kg = "kg", } export enum Vendor { FutureWorld = "Future World", } ``` La función para hacer la petición **HTTP** quedaría de la siguiente manera: ```js const getProducts = async (): Promise<Product[]> => { const response: Products = await fetch( `${process.env.SHOPIFY_HOSTNAME}/admin/api/2023-10/products.json`, { headers: { "X-Shopify-Access-Token": process.env.SHOPIFY_API_KEY!, }, } ).then((resp) => resp.json()); return response.products; }; ```No olvidar configurar sus remote imagesPatterns: ![](https://static.platzi.com/media/user_upload/image-107d645b-6bc5-46dc-9054-1fa4065c8e76.jpg)
Dejo por acá la interfaz para mapear la respuesta al endpoint de productos ```js export interface Products { products: Product[]; } export interface Product { id: number; title: string; body_html: string; vendor: Vendor; product_type: string; created_at: Date; handle: string; updated_at: Date; published_at: Date; 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: Date; updated_at: Date; 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: Date; updated_at: Date; taxable: boolean; barcode: null; grams: number; weight: number; weight_unit: WeightUnit; inventory_item_id: number; inventory_quantity: number; old_inventory_quantity: number; requires_shipping: boolean; admin_graphql_api_id: string; image_id: null; } export enum FulfillmentService { Manual = "manual", } export enum InventoryPolicy { Deny = "deny", } export enum WeightUnit { Kg = "kg", } export enum Vendor { FutureWorld = "Future World", } ```La función para la petición HTTP quedaría de la siguiente manera: ```js const getProducts = async (): Promise<Product[]> => { const response: Products = await fetch( `${process.env.SHOPIFY_HOSTNAME}/admin/api/2023-10/products.json`, { headers: { "X-Shopify-Access-Token": process.env.SHOPIFY_API_KEY!, }, } ).then((resp) => resp.json()); return response.products; }; ```No olvidar configurar los remoteImages en su archivo next.config.mjs ```js images: { remotePatterns: [ { protocol: 'https', hostname: ' cdn.shopify.com', pathname: '/s/files/1/0888/5816/5540/files/*', }, ] } ```
Tengo un error, en el anterior comentario no se subió mi imagen, dejo de todas formas el error redactado ![](</Users/javierdamiani/Desktop/Captura de pantalla 2024-04-04 a la(s) 3.15.18 p. m..png>) Unhandled Runtime Error Error: Unexpected token '<', "\<html> <"... is not valid JSON ## Source src/components/home/MainProducts/MainProducts.tsx (8:15) @ async getProducts 6 | }); 7 | /\* eslint-disable \*/console.log(...oo\_oo(`264013000\_7\_2\_7\_47\_4`,"Respuesta de la API:", response)); \> 8 | const data = await response.json(); | ^ 9 | return data; 10 | }; 11 | ## Call Stack ### async MainProducts src/components/home/MainProducts/MainProducts.tsx (13:19)
Tengo un error y la verdad no tengo idea de cómo solucionarlo. Alguien me podría ayudar por favor. 04/04/2024![](https://ibb.co/n7HD102)

Clave secreta de API de Shopify

Aca una forma de crear la funcion sin instanciar Header y usar headers : ```js const response = await fetch(`${process.env.SHOPIFY_HOSTNAME}/admin/api/2023-10/products.json`,{ headers: { 'X-Shopify-Access-Token':process.env.SHOPIFY_API_KEY || "" } }); ```const response = await fetch(`${process.env.SHOPIFY\_HOSTNAME}/admin/api/2023-10/products.json`,{        headers: {            'X-Shopify-Access-Token':process.env.SHOPIFY\_API\_KEY || ""        }    });