Usaremos React, Tailwind CSS e posts em Markdown
yarn create next-app -e with-tailwindcss blog
cd blog
yarn add react-markdown gray-matter @tailwindcss/typography
posts
na raiz do projeto e dentro, crie um arquivo chamado hello-world.md
com
o conteúdo que você desejar:---
title: "Hello World"
date: "07/12/2021"
---
Olá mundo!
**Texto em negrito** e _texto em itálico_.
- Lista
- com
- vários
- items
---
) é o que chamamos de
"Front Matter" e é onde geralmente definimos metadados do post. Esse bloco precisa necessariamente
estar no começo do arquivo e precisa usar uma sintaxe YAML válida.title: "Hello World"
date: "07/12/2021"
<Head>
do arquivo pages/index.js
para pages/_app.js
e renomear o
conteúdo da tag <title>
:pages/_app.js
import Head from "next/head";
import "tailwindcss/tailwind.css";
function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<title>Meu blog</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Component {...pageProps} />
</>
);
}
export default MyApp;
Posts
na página inicial:pages/index.js
const Blog = () => {
return <h1>Posts</h1>;
};
export default Blog;
yarn dev
<header>
e <main>
ao arquivo pages/_app.js
com algumas classes do
Tailwind CSS: import Head from "next/head";
+import Link from "next/link";
+
import "tailwindcss/tailwind.css";
function MyApp({ Component, pageProps }) {
@@ -9,7 +11,17 @@ function MyApp({ Component, pageProps }) {
<link rel="icon" href="/favicon.ico" />
</Head>
- <Component {...pageProps} />
+ <header className="py-10 bg-gradient-to-r from-green-400 to-blue-500 text-center">
+ <Link href="/">
+ <a>
+ <h2 className="text-5xl font-bold text-white">Meu blog</h2>
+ </a>
+ </Link>
+ </header>
+
+ <main className="my-6 mx-auto p-6 bg-white sm:shadow-lg rounded prose lg:prose-xl">
+ <Component {...pageProps} />
+ </main>
</>
);
}
@tailwindcss/typography
.tailwind.config.js
e adicione isso: darkMode: false, // or 'media' or 'class'
theme: {
- extend: {},
+ extend: {
+ typography: {
+ DEFAULT: {
+ css: {
+ a: {
+ color: "#3182ce",
+ "&:hover": {
+ color: "#2c5282",
+ },
+ },
+ },
+ },
+ },
+ },
},
variants: {
extend: {},
},
- plugins: [],
+ plugins: [require("@tailwindcss/typography")],
};
<body>
pages/_document.js
com o seguinte conteúdo:import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head />
<body className="bg-white sm:bg-gray-50">
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
bg-white
e sm:bg-gray-50
na tag <body>
.pages/_document.js
na documentação oficial.lib/posts.js
que será responsável por ler os arquivos Markdown e retornar uma
lista de posts:import { promises as fs } from "fs";
import path from "path";
import matter from "gray-matter";
const getPosts = async () => {
const postsDirectory = path.join(process.cwd(), "posts");
const filenames = await fs.readdir(postsDirectory);
return await Promise.all(
filenames.map(async (filename) => {
const filePath = path.join(postsDirectory, filename);
const fileContents = await fs.readFile(filePath, "utf8");
const document = matter(fileContents);
return {
slug: filename.replace(/\.md$/, ""),
title: document.data.title,
date: document.data.date,
markdown: document.content,
};
})
);
};
export default getPosts;
.md
).gray-matter
para ler os metadados definidos no
front matter de cada post.getPosts
do arquivo que acabamos de criar na página inicial, ou seja, no
arquivo pages/index.js
e fazer um loop para exibir links e títulos de cada um dos posts:import Link from "next/link";
import getPosts from "../lib/posts";
const Blog = ({ posts }) => {
return (
<>
<h1>Posts</h1>
<ul>
{posts.map(({ slug, title }) => (
<li key={slug}>
<Link href={`/${slug}`}>
<a>{title}</a>
</Link>
</li>
))}
</ul>
</>
);
};
export async function getStaticProps() {
return {
props: {
posts: await getPosts(),
},
};
}
export default Blog;
pages/[slug].js
.import getPosts from "../lib/posts";
const Post = ({ title, date, markdown }) => (
<article>
<h1>{title}</h1>
<time className="font-extralight tracking-wider text-gray-500">{date}</time>
{markdown}
</article>
);
export const getStaticPaths = async () => {
const posts = await getPosts();
return {
paths: posts.map((post) => `/${post.slug}`),
fallback: false,
};
};
export const getStaticProps = async ({ params: { slug } }) => {
const posts = await getPosts();
const post = posts.find((post) => post.slug === slug);
return { props: post };
};
export default Post;
ReactMarkdown
para fazer isso. Atualize o arquivo pages/[slug].js
para:+import ReactMarkdown from "react-markdown";
+
import getPosts from "../lib/posts";
const Post = ({ title, date, markdown }) => (
<article>
<h1>{title}</h1>
<time className="font-extralight tracking-wider text-gray-500">{date}</time>
- {markdown}
+ <ReactMarkdown>{markdown}</ReactMarkdown>
</article>
);
public/
.public/vercel.svg
, que atualmente não tem utilidade nenhuma.