Next.js ofrece herramientas poderosas para optimizar tanto tipografías como imágenes, mejorando significativamente el rendimiento y la experiencia del usuario.
Next.js 13+ incluye next/font que optimiza automáticamente las fuentes y elimina el layout shift.
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="es" className={inter.className}>
<body>{children}</body>
</html>
)
} // app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="es" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
} // lib/fonts.ts
import {
Inter,
Poppins,
Source_Code_Pro,
Playfair_Display
} from 'next/font/google'
export const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
export const poppins = Poppins({
weight: ['300', '400', '500', '600', '700'],
subsets: ['latin'],
display: 'swap',
variable: '--font-poppins',
})
export const sourceCodePro = Source_Code_Pro({
subsets: ['latin'],
display: 'swap',
variable: '--font-source-code-pro',
})
export const playfairDisplay = Playfair_Display({
subsets: ['latin'],
display: 'swap',
variable: '--font-playfair',
}) Las fuentes locales se importan usando next/font/local y se configuran similarmente a las fuentes de Google.
// lib/fonts.ts
import localFont from 'next/font/local'
export const customFont = localFont({
src: [
{
path: '../public/fonts/CustomFont-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: '../public/fonts/CustomFont-Bold.woff2',
weight: '700',
style: 'normal',
},
{
path: '../public/fonts/CustomFont-Italic.woff2',
weight: '400',
style: 'italic',
},
],
variable: '--font-custom',
display: 'swap',
}) // app/layout.tsx
import { inter, poppins, customFont } from '../lib/fonts'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html
lang="es"
className={`${inter.variable} ${poppins.variable} ${customFont.variable}`}
>
<body className={inter.className}>
{children}
</body>
</html>
)
} Basta agregar las variables CSS a :root y definir las clases utilitarias para cada fuente.
/* globals.css */
:root {
--font-inter: 'Inter', sans-serif;
--font-poppins: 'Poppins', sans-serif;
--font-source-code-pro: 'Source Code Pro', monospace;
--font-playfair: 'Playfair Display', serif;
--font-custom: 'CustomFont', sans-serif;
}
/* Clases utilitarias */
.font-inter { font-family: var(--font-inter); }
.font-poppins { font-family: var(--font-poppins); }
.font-mono { font-family: var(--font-source-code-pro); }
.font-serif { font-family: var(--font-playfair); }
.font-custom { font-family: var(--font-custom); }
/* Configuración por defecto */
body {
font-family: var(--font-inter);
}
h1, h2, h3 {
font-family: var(--font-poppins);
}
code, pre {
font-family: var(--font-source-code-pro);
} // components/Typography.tsx
import { inter, poppins, sourceCodePro } from '../lib/fonts'
export function Typography() {
return (
<div>
<h1 className={poppins.className}>
Título con Poppins
</h1>
<p className={inter.className}>
Párrafo con Inter para mejor legibilidad
</p>
<code className={sourceCodePro.className}>
const code = "Source Code Pro";
</code>
{/* Usando variables CSS */}
<div className="font-custom">
Texto con fuente personalizada
</div>
</div>
)
} Las imágenes en Next.js se optimizan automáticamente y se cargan de manera eficiente.
// components/Hero.tsx
import Image from 'next/image'
export function Hero() {
return (
<div className="hero">
<Image
src="/hero-image.jpg"
alt="Descripción de la imagen"
width={800}
height={600}
priority
/>
</div>
)
} Next.js soporta imágenes responsivas usando el atributo sizes y el atributo fill. Esto permite que la imagen se adapte a diferentes tamaños de pantalla.
// components/ResponsiveImage.tsx
import Image from 'next/image'
export function ResponsiveImage() {
return (
<div className="image-container">
<Image
src="/responsive-image.jpg"
alt="Imagen responsiva"
fill
style={{
objectFit: 'cover',
}}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
)
} // components/Gallery.tsx
import Image from 'next/image'
const images = [
{ src: '/gallery/image1.jpg', alt: 'Imagen 1' },
{ src: '/gallery/image2.jpg', alt: 'Imagen 2' },
{ src: '/gallery/image3.jpg', alt: 'Imagen 3' },
]
export function Gallery() {
return (
<div className="gallery">
{images.map((image, index) => (
<div key={index} className="gallery-item">
<Image
src={image.src}
alt={image.alt}
width={400}
height={300}
loading={index < 2 ? 'eager' : 'lazy'}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
/>
</div>
))}
</div>
)
} // Componente tradicional img
function TraditionalImage() {
return (
<img
src="/traditional-image.jpg"
alt="Imagen tradicional"
width="800"
height="600"
/>
)
}
// Componente Next.js Image
function OptimizedImage() {
return (
<Image
src="/optimized-image.jpg"
alt="Imagen optimizada"
width={800}
height={600}
placeholder="blur"
blurDataURL="/placeholder.jpg"
/>
)
} | Característica | <img> | <Image> |
|---|---|---|
| Lazy Loading | Manual | Automático |
| Optimización | No | Sí (WebP, AVIF) |
| Layout Shift | Posible | Previene |
| Responsive | Manual | Automático |
| Placeholder | No | Blur, shimmer |
| Performance | Básico | Optimizado |
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['example.com', 'cdn.example.com'],
remotePatterns: [
{
protocol: 'https',
hostname: '**.amazonaws.com',
port: '',
pathname: '/images/**',
},
],
formats: ['image/webp', 'image/avif'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
minimumCacheTTL: 60,
},
}
module.exports = nextConfig // components/OptimizedGallery.tsx
import Image from 'next/image'
export function OptimizedGallery() {
return (
<div className="optimized-gallery">
{/* Imagen con prioridad para above-the-fold */}
<Image
src="/hero.jpg"
alt="Imagen principal"
width={1200}
height={800}
priority
quality={90}
/>
{/* Imagen con lazy loading */}
<Image
src="/content.jpg"
alt="Contenido"
width={600}
height={400}
loading="lazy"
quality={75}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
{/* Imagen externa */}
<Image
src="https://cdn.example.com/image.jpg"
alt="Imagen externa"
width={400}
height={300}
unoptimized={false}
/>
</div>
)
} /* styles/images.css */
.image-container {
position: relative;
width: 100%;
height: 400px;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
padding: 1rem;
}
.gallery-item {
position: relative;
aspect-ratio: 4/3;
overflow: hidden;
border-radius: 8px;
}
.gallery-item img {
transition: transform 0.3s ease;
}
.gallery-item:hover img {
transform: scale(1.05);
}
/* Responsive images */
@media (max-width: 768px) {
.image-container {
height: 250px;
}
.gallery {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
} Usa priority solo para imágenes above-the-fold y loading="lazy" para el resto. Configura quality entre 75-85 para balance óptimo.
Combina máximo 2-3 familias tipográficas. Usa display: 'swap' para evitar FOIT y mejora la experiencia del usuario.
Siempre incluye alt descriptivos en imágenes y usa width/height para prevenir layout shift y mejorar Core Web Vitals.
Configura remotePatterns para imágenes externas y usa formatos modernos (WebP, AVIF) para reducir el tamaño hasta 50%.