Creación de un blog con Next.js
En este artículo, usaremos Next.js para crear un marco de blog estático con el diseño y la estructura inspirados en Jekyll. Siempre ha sido un gran admirador de cómo Jekyll facilita a los principiantes la configuración de un blog y, al mismo tiempo, proporciona también un gran grado de control sobre cada aspecto del blog para los usuarios avanzados.
Con la introducción de Next.js en los últimos años, combinada con la popularidad de React, existe una nueva vía para explorar los blogs estáticos. Next.js hace que sea muy fácil crear sitios web estáticos basados en el propio sistema de archivos con poca o ninguna configuración requerida.
La estructura de directorios de un típico blog básico de Jekyll se ve así:
.├─── _posts/ ...blog posts in markdown├─── _layouts/ ...layouts for different pages├─── _includes/ ...re-usable components├─── index.md ...homepage└─── config.yml ...blog config
La idea es diseñar nuestro marco en torno a esta estructura de directorios tanto como sea posible para que sea más fácil migrar un blog desde Jekyll simplemente reutilizando las publicaciones y ajustes definidos en el blog.
Para aquellos que no se conocen con Jekyll, es un generador de sitios estáticos que puede transformar texto sin formato en blogs y sitios web estáticos. Consulte la guía de inicio rápido para comenzar a utilizar Jekyll.
Este artículo también supone que tienes conocimientos básicos de React. De lo contrario, la página de inicio de React es un buen lugar para comenzar.
Instalación
Next.js funciona con React y está escrito en Node.js. Por lo tanto, primero debemos instalar npm, antes de agregarlo next
y al proyecto.react
react-dom
mkdir nextjs-blog cd $_npm init -ynpm install next react react-dom --save
Para ejecutar scripts Next.js en la línea de comando, tenemos que agregar el next
comando a la scripts
sección de nuestro archivo package.json
.
"scripts": { "dev": "next"}
Ahora podemos ejecutar npm run dev
en la línea de comando por primera vez. Veamos qué pasa.
$ npm run dev nextjs-blog@1.0.0 dev /~user/nextjs-blog nextready - started server on http://localhost:3000Error: Couldn't find a `pages` directory. Please create one under the project root
El compilador se queja de que falta un directorio de páginas en la raíz del proyecto. Aprenderemos sobre el concepto de páginas en la siguiente sección.
Concepto de pagina
Next.js se basa en el concepto de páginas. Cada página es un componente de React que puede ser de tipo .js
o .jsx
que está asignado a una ruta según el nombre del archivo. Por ejemplo:
File Route---- -----/pages/about.js /about/pages/projects/work1.js /projects/work1/pages/index.js /
Creemos el pages
directorio en la raíz del proyecto y completamos nuestra primera página, index.js
con un componente básico de React.
// pages/index.jsexport default function Blog() { return divWelcome to the Next.js blog/div}
Ejecute npm run dev
una vez más para iniciar el servidor y navegue http://localhost:3000
en el navegador para ver su blog por primera vez.
Fuera de la caja obtenemos:
- Recarga en caliente para que no tengamos que actualizar el navegador para cada cambio de código.
- Generación estática de todas las páginas dentro del
/pages/**
directorio. - Archivo estático que sirve para los activos que se encuentran en el
/public/**
directorio. - Página de error 404.
Navegue hasta una ruta aleatoria en localhost para ver la página 404 en acción. Si necesita una página 404 personalizada, los documentos de Next.js tienen excelente información.
Paginas dinamicas
Las páginas con rutas estáticas son útiles para crear la página de inicio, la página de información, etc. Sin embargo, para crear dinámicamente todas nuestras publicaciones, usaremos la capacidad de ruta dinámica de Next.js. Por ejemplo:
File Route---- -----/pages/posts/[slug].js /posts/1 /posts/abc /posts/hello-world
Cualquier ruta, como /posts/1
, /posts/abc
, etc., coincidirá con /posts/[slug].js
y el parámetro slug se enviará como parámetro de consulta a la página. Esto es especialmente útil para las publicaciones de nuestro blog porque no queremos crear un archivo por publicación; en su lugar, podríamos pasar dinámicamente el slug para representar la publicación correspondiente.
Anatomía de un blog.
Ahora que entendemos los componentes básicos de Next.js, definimos la anatomía de nuestro blog.
.├─ api│ └─ index.js # fetch posts, load configs, parse .md files etc├─ _includes│ ├─ footer.js # footer component│ └─ header.js # header component├─ _layouts│ ├─ default.js # default layout for static pages like index, about│ └─ post.js # post layout inherts from the default layout├─ pages│ ├─ index.js # homepage| └─ posts # posts will be available on the route /posts/| └─ [slug].js # dynamic page to build posts└─ _posts ├─ welcome-to-nextjs.md └─ style-guide-101.md
API de blogs
Un marco de blog básico necesita dos funciones API:
- Una función para recuperar los metadatos de todas las publicaciones en el
_posts
directorio. - Una función para buscar una sola publicación para un determinado
slug
con el HTML y los metadatos completos.
Opcionalmente, también nos gustaría que toda la configuración del sitio definida config.yml
esté disponible en todos los componentes. Entonces necesitamos una función que analice la configuración YAML en un objeto nativo.
Dado que estaríamos tratando con muchos archivos que no son JavaScript, como Markdown ( .md
), YAML ( .yml
), etc., usaremos la raw-loader
biblioteca para cargar dichos archivos como cadenas para que sea más fácil procesarlos.
npm install raw-loader --save-dev
A continuación, debemos decirle a Next.js que use raw-loader cuando importemos formatos de archivos .md y .yml creando un next.config.js
archivo en la raíz del proyecto (más información al respecto).
module.exports = { target: 'serverless', webpack: function (config) { config.module.rules.push({test: /.md$/, use: 'raw-loader'}) config.module.rules.push({test: /.yml$/, use: 'raw-loader'}) return config }}
Next.js 9.4 introdujo alias para importaciones relativas, lo que ayuda a limpiar los espaguetis de las declaraciones de importación causadas por rutas relativas. Para utilizar alias, cree un jsconfig.json
archivo en el directorio raíz del proyecto especificando la ruta base y todos los alias de módulo necesarios para el proyecto.
{ "compilerOptions": { "baseUrl": "./", "paths": { "@includes/*": ["_includes/*"], "@layouts/*": ["_layouts/*"], "@posts/*": ["_posts/*"], "@api": ["api/index"], } }}
Por ejemplo, esto nos permite importar nuestros diseños simplemente usando:
import DefaultLayout from '@layouts/default'
Obtener todas las publicaciones
Esta función leerá todos los archivos Markdown en el _posts
directorio, analizará el texto inicial definido al comienzo de la publicación usando materia gris y devolverá la matriz de metadatos para todas las publicaciones.
// api/index.jsimport matter from 'gray-matter'
export async function getAllPosts() { const context = require.context('../_posts', false, /.md$/) const posts = [] for(const key of context.keys()){ const post = key.slice(2); const content = await import(`../_posts/${post}`); const meta = matter(content.default) posts.push({ slug: post.replace('.md',''), title: meta.data.title }) } return posts;}
Una publicación típica de Markdown se ve así:
---title: "Welcome to Next.js blog!"---**Hello world**, this is my first Next.js blog post and it is written in Markdown.I hope you like it!
La sección descrita por ---
se llama portada y contiene los metadatos de la publicación, como título, enlace permanente, etiquetas, etc. Aquí está el resultado:
[ { slug: 'style-guide-101', title: 'Style Guide 101' }, { slug: 'welcome-to-nextjs', title: 'Welcome to Next.js blog!' }]
Asegúrese de instalar primero la biblioteca de materia gris desde npm usando el comando npm install gray-matter --save-dev
.
Obtener una sola publicación
Para un slug determinado, esta función ubicará el archivo en el _posts
directorio, analizará Markdown con la biblioteca marcada y devolverá el HTML de salida con metadatos.
// api/index.jsimport matter from 'gray-matter'import marked from 'marked'
export async function getPostBySlug(slug) { const fileContent = await import(`../_posts/${slug}.md`) const meta = matter(fileContent.default) const content = marked(meta.content) return { title: meta.data.title, content: content }}
Salida de muestra:
{ title: 'Style Guide 101', content: 'pIncididunt cupidatat eiusmod .../p'}
Asegúrese de instalar primero la biblioteca marcada desde npm usando el comando npm install marked --save-dev
.
configuración
Para reutilizar la configuración de Jekyll para nuestro blog Next.js, analizaremos el archivo YAML usando la js-yaml
biblioteca y exportaremos esta configuración para que pueda usarse en todos los componentes.
// config.ymltitle: "Next.js blog"description: "This blog is powered by Next.js"
// api/index.jsimport yaml from 'js-yaml'export async function getConfig() { const config = await import(`../config.yml`) return yaml.safeLoad(config.default)}
Asegúrese de instalar js-yaml
desde npm primero usando el comando npm install js-yaml --save-dev
.
Incluye
Nuestro _includes
directorio contiene dos componentes básicos de React, Header
y Footer
, que se utilizarán en los diferentes componentes de diseño definidos en el _layouts
directorio.
// _includes/header.jsexport default function Header() { return headerpBlog | Powered by Next.js/p/header}
// _includes/footer.jsexport default function Footer() { return footerp©2020 | Footer/p/footer}
Diseños
Tenemos dos componentes de diseño en el _layouts
directorio. Uno es DefaultLayout
cuál es el diseño base sobre el cual se construirán todos los demás componentes del diseño.
// _layouts/default.jsimport Head from 'next/head'import Header from '@includes/header'import Footer from '@includes/footer'
export default function DefaultLayout(props) { return ( main Head title{props.title}/title meta name='description' content={props.description}/ /Head Header/ {props.children} Footer/ /main )}
El segundo diseño es el PostLayout
componente que anulará el título definido en el DefaultLayout
título de la publicación y representará el HTML de la publicación. También incluye un enlace a la página de inicio.
// _layouts/post.jsimport DefaultLayout from '@layouts/default'import Head from 'next/head'import Link from 'next/link'
export default function PostLayout(props) { return ( DefaultLayout Head title{props.title}/title /Head article h1{props.title}/h1 div dangerouslySetInnerHTML={{__html:props.content}}/ divLink href='/'aHome/a/Link/div /article /DefaultLayout )}
next/head
es un componente integrado para agregar elementos a head
la página. next/link
es un componente integrado que maneja las transiciones del lado del cliente entre las rutas definidas en el directorio de páginas.
Página principal
Como parte de la página de índice, también conocida como página de inicio, enumeraremos todas las publicaciones dentro del _posts
directorio. La lista contendrá el título de la publicación y el enlace permanente a la página de la publicación individual. La página de índice usará DefaultLayout
e importaremos la configuración en la página de inicio para pasar title
y description
al diseño.
// pages/index.jsimport DefaultLayout from '@layouts/default'import Link from 'next/link'import { getConfig, getAllPosts } from '@api'
export default function Blog(props) { return ( DefaultLayout title={props.title} description={props.description} pList of posts:/p ul {props.posts.map(function(post, idx) { return ( li key={idx} Link href={'/posts/'+post.slug} a{post.title}/a /Link /li ) })} /ul /DefaultLayout )}
export async function getStaticProps() { const config = await getConfig() const allPosts = await getAllPosts() return { props: { posts: allPosts, title: config.title, description: config.description } }}
getStaticProps
se llama en el momento de la compilación para prerenderizar páginas pasando props
al componente predeterminado de la página. Usamos esta función para obtener la lista de todas las publicaciones en el momento de la compilación y representar el archivo de publicaciones en la página de inicio.
Publicar página
Esta página mostrará el título y el contenido de la publicación slug
proporcionada como parte del context
. La página de publicación utilizará el PostLayout
componente.
// pages/posts/[slug].jsimport PostLayout from '@layouts/post'import { getPostBySlug, getAllPosts } from "@api"
export default function Post(props) { return PostLayout title={props.title} content={props.content}/}
export async function getStaticProps(context) { return { props: await getPostBySlug(context.params.slug) }}
export async function getStaticPaths() { let paths = await getAllPosts() paths = paths.map(post = ({ params: { slug:post.slug } })); return { paths: paths, fallback: false }}
Si una página tiene rutas dinámicas, Next.js necesita conocer todas las rutas posibles en el momento de la compilación. getStaticPaths
proporciona la lista de rutas que deben representarse en HTML en el momento de la compilación. La propiedad alternativa garantiza que si visita una ruta que no existe en la lista de rutas, devolverá una página 404.
Listo para la producción
Agregue los siguientes comandos para build
y start
en package.json
, en la scripts
sección y luego ejecútelos npm run build
seguidos de npm run start
para crear el blog estático e iniciar el servidor de producción.
// package.json"scripts": { "dev": "next", "build": "next build", "start": "next start"}
El código fuente completo de este artículo está disponible en este repositorio de GitHub. Siéntete libre de clonarlo localmente y jugar con él. El repositorio también incluye algunos marcadores de posición básicos para aplicar CSS a tu blog.
Mejoras
El blog, aunque funcional, quizás sea demasiado básico para la mayoría de los casos promedio. Sería bueno ampliar el marco o enviar un parche para incluir algunas características más como:
- Paginación
- Resaltado de sintaxis
- Categorías y etiquetas para publicaciones
- Estilo
En general, Next.js parece realmente muy prometedor para crear sitios web estáticos, como un blog. Combinado con su capacidad para exportar HTML estático, podemos crear una aplicación verdaderamente independiente sin la necesidad de un servidor.
Deja un comentario