Серия статей, в которой я объясню как запустить свой блог, если у вас уже есть локально хранящиеся файлы в md формате
- Блог на Gatsby + Obsidian from scratch ч.1 <-- вы тут
- Блог на Gatsby + Obsidian from scratch ч.2
- Блог на Gatsby + Obsidian from scratch ч.3
- Блог на Gatsby + Obsidian from scratch ч.4
- Блог на Gatsby и Obsidian ч.5
- Блог на Gatsby. Rss лента ч.6
- Блог на Gatsby. Разные изображения для микроразметки ч.7
Подразумевается, что у вас уже есть:
- файлики со статьями в
.mdформате, пишите вы их в Obsidian или где-то еще - неважно, но я буду использовать плагин, специфичный для obsidian (возможно он вам не понадобится) - желание создать блог
- минимальные знания
jsиreactв частности. Минимальных знаний должно хватить, чтобы подстроить шаблон блога под себя. - установлены последняя версия nodejs и npm (на момент написания статьи, требуется node v18) и текстовый редактор (я предпочитаю VS Code)
Для тех, кому лень все это читать и вникать, а хочется просто взять и чтобы все само заработало, вот ссылка на мой репозиторий, вам нужно будет только прилинковать свой Obsidian vault (в ридми описано как, и в этой статье кстати тоже), ну и заменить ваши контактные данные и фото профиля. Если не лень, то вам надо читать дальше)
Введение
Я начал писать заметки в Obsidian для себя, так как считаю его для себя одним из лучших решений (не то что бы у меня богатый опыт). Он позволяет писать в cтатьи и заметки в markdown синтаксисе и хранит их в неизменном виде. Поэтому я быстро решил (спустя 2 десятка заметок), что пора сделать их (заметки) достоянием общественности, превратив их в блог. А сам процесс создания я опишу в этой статье, наверняка кто-то еще захочет провернуть то же самое 😁
В качестве платформы для блога я сначала решил использовать eleventy, но спустя 10 минут у меня не получилось нормально скопировать изображения и я решил использовать решение, которое я использовал раньше для создания сайта-визитки для своего знакомого - Gatsby
Формат статей
- В статьях обязательно наличие поля date в формате frontmatter
Frontmatter - это способ идентификации метаданных в файлах Markdown. Метаданные могут быть буквально тем, что вы хотите, но часто они используются для элементов данных, необходимых вашей странице, и вы не хотите отображать их напрямую.
Эти данные должны располагаться в самом начале
.mdфайлов, дата позволит сортировать блоги по дате
---
date: MONTH/DAY/YEAR
---- Ссылки на изображения должны быть в формате
[image alt]('./images/name-of-image.(jpeg|gif|png)')☝️ Это важно, потому что у меня так и не получилось заставить работать формат
![[some-image.(jpeg|gif|png)]]- Ссылки на соседние статьи в формате, который используется в Obsidian будут работать корректно
[[Some post title]]Начало. Создаем директорию и накатываем GatsbyJs
npx gatsby new blog https://github.com/gatsbyjs/gatsby-starter-hello-world
cd blogПроверяем, что все работает: запускаем в терминале npm run develop - после того, как в терминале появится ссылка http://localhost:8080 - тыкаем и переходим по ней или просто открываем в браузере. Если все ок, то мы должны увидеть надпись hello world
Поздравляю, ваш блог готов! 😁 (шутка, дальше мы заставим его отображать наши посты из obsidian)
Создаем символьную ссылку на ваши статьи
Предположим, что ваши статьи находятся по такому пути:
/Users/yourUsername/Documents/knowledgebase/
Теперь создадим символьную ссылку таким образом:
ln -s /Users/yourUsername/Documents/knowledgebase/ ./contentОткрываем в вашем редакторе проект с блогом и видим, что в директории ./content появилаcь директория knowledgebase в которой хранятся статьи
Устанавливаем необходимые плагины
npm i gatsby-source-filesystem gatsby-transformer-remark gatsby-remark-obsidianРедактируем конфигурацию блога
Открываем файл gatsby-config.js, вот такое содержимое получилось у меня:
/**
* Configure your Gatsby site with this file.
*
* See: https://www.gatsbyjs.com/docs/reference/config-files/gatsby-config/
*/
/**
* @type {import('gatsby').GatsbyConfig}
*/
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content/knowledgebase`,
name: `blog`,
ignore: [`**/\.obsidian`]
},
},
{
resolve: "gatsby-transformer-remark",
options: {
plugins: [
{
resolve: 'gatsby-remark-obsidian',
options: {
titleToURL: (title) => `/${title}`, // optional
markdownFolder: `${__dirname}/content/knowledgebase`, // optional
highlightClassName: 'highlight', // optional
},
},
]
}
},
],
}```
### Проверяем, как выглядят данные в /graphql
Запускаем
```sh
npm run developПосле того как все соберется (если все прошло гладко) вы увидите в терминале ссылки на зачаток блога и ссылку на Браузерный graphql playground, где можно накликать запрос и посмотреть в каком виде будут доступны данные:
You can now view gatsby-starter-hello-world in the browser.
⠀
http://localhost:8000/
⠀
View GraphiQL, an in-browser IDE, to explore your site's data and schema
⠀
http://localhost:8000/___graphql
⠀Переходим по этой ссылке http://localhost:8000/___graphql и видим примерно следующее
Открываем GraphiQL Explorer и там можно выбрать из любых данных, в зависимости от того, какие плагины вы наподключали, нас же интересуют наши посты в markdown и как они трансформировались в html.
Кликаем на allMarkdownRemark и там необходимо накликать необходимые поля, из тех, что точно понадобятся excerpt (чтобы показывать превью текста) и html чтобы рендерить полный текст статьи, вот какой запрос получился у меня:
query MyQuery {
allMarkdownRemark {
edges {
node {
excerpt(format: HTML)
html
frontmatter {
date
title
}
id
}
}
}
}И вот как все выгдядит, когда я нажимаю на кнопочку play (которая красная)
Круто, мы видим, что наш markdown трансформировался в html - а значит пора сделать так, чтобы посты отображались в браузере, вместо надписи hello world
Но перед этим я добавлю немного мета информации для блога в gatsby-config.js
module.exports = {
siteMetadata: {
social: {
twitter: 'https://twitter.com/the_strange_dev',
siteUrl: 'https://thestrangeadventurer.com',
twitterUsername: '@the_strange_dev',
profileImage: '/profile.jpg'
},
title: 'Блог беспечного авантюриста',
description: 'Яндексойд, путешественник, авантюрист, беспечный и непредсказуемый. Люблю путешествовать, писать и делиться своими историями. Все мои путешествия и истории о них вы можете найти здесь (шутка, тут только про код).',
},
// ...остальной кодСвою аватарку я положил в директорию /static и назвал profile.jpg, все эти метаданные тоже можно достать с помощью graqhql запроса
🤬 А че с ними делать, с этими запросами? Об этому чуть позже, все по порядку)
Создаем странички и рендерим наши посты
Дальше задача такая:
- создать на главной список всех постов с постраничной навигацией
- создать все страницы с постами
В Gatsby можно создавать страницы двумя способами:
- Статичными (физически кладем файлик в src/pages, наполняем его и страница готова)
- Динамически - тут есть два
стуласпособа:- API для файлового роутинга
- Программное создание страничек с помощью файлика
gatsby-node.js
Так как у меня раньше был небольшой опыт с Gatsby я решил не переизобретать велосипед и сделать все уже знакомым мне способои и все делал по документации .
Ниже приведу пример файлов, которые нужно создать и опишу код, который они содержат:
gatsby-node.js в корне проекта
Во-первых, нам понадобится пакет slugify, чтобы конвертировать названия на русском в человекопонятные слаги для построения ссылок, так ссылки выглядят более аккуратно и у меня есть ничем неподкрепленное убеждение, что это положительно влияет на SEO 😁
Устанавливаем пакет: npm install slugify
Дальше привожу пример кода с максимально подробными комментариями
const path = require("path")
const { createFilePath } = require("gatsby-source-filesystem")
/*
* npm install slugify - пакет нужен чтобы превращать заголовки в слаги на латинице
* Например "Привет мир" => "privet-mir"
*/
const slugify = require("slugify");
/**
* Создаем поля slug и title для каждого поста
* slug - это путь к посту, например /privet-mir
* title - это заголовок поста, например "Привет мир"
* Функция вызывается при создании каждого поста
*/
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode });
createNodeField({
name: `slug`,
node,
value: slugify(value, { lower: true }),
})
createNodeField({
name: `title`,
node,
value: value,
})
}
}
/**
* Создаем все необходимые страницы на основе файлов
* которые лежат в папке src/templates
*/
exports.createPages = async function ({ actions, graphql }) {
/**
* Получаем слаги, которые необходимы
* чтобы построить ссылки на все посты
* и понять сколько будет всего страниц
*/
const { data } = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
const posts = data.allMarkdownRemark.edges
const POSTS_PER_PAGE = 5 // Определяем сколько постов будет на одной странице
const numPages = Math.ceil(posts.length / POSTS_PER_PAGE) // Считаем сколько всего страниц
/**
* Создаем страницу для каждого поста
* в качестве компонента, который будет отвечать за рендеринг
* указываем src/templates/BlogPost.js
*/
data.allMarkdownRemark.edges.forEach((edge) => {
const { slug, title } = edge.node.fields
actions.createPage({
path: slug,
component: path.resolve(process.cwd(), `src/templates/BlogPost.js`),
context: { slug, title },
})
})
/**
* Создаем страницы для пагинации
* если страница 1, то путь будет /
* если страница 2, то путь будет /page/2 и т.д.
* в качестве компонента, который будет отвечать за рендеринг
* указываем src/templates/BlogList.js
*/
Array.from({ length: numPages }).forEach((_, i) => {
actions.createPage({
path: i === 0 ? `/` : `/page/${i + 1}`,
component: path.resolve(process.cwd(), `src/templates/BlogList.js`),
/**
* Передаем в контекст данные для построения пагинации
*/
context: {
limit: POSTS_PER_PAGE,
skip: i * POSTS_PER_PAGE,
numPages,
currentPage: i + 1,
},
})
})
}
И так, мы описали, какие страницы мы создаем, а теперь нужно создать директорию /src/templates и положить туда два файлика:
- Bloglist.js
- BlogPost.js
Там будет код компонентов и graphql запросы для получения данных, код и комментариии ниже:
src/templates/BlogList.js
// src/templates/BlogList.js
import React from "react"
import { graphql, Link } from "gatsby"
/**
* Запрос для получения списка постов, отсортированных по дате
*
* Так как Gatsby это фреймворк - он предоставляет нам определенный API
* Если мы экспортируем из файла graphql запрос - то Gatsby автоматически
* будет его выполнять во время сборки и передавать результат
* в компонент в props.data
*/
export const blogListQuery = graphql`
query BlogListQuery($skip: Int!, $limit: Int!) {
allMarkdownRemark(
sort: { frontmatter: { date: DESC }}
limit: $limit
skip: $skip
) {
edges {
node {
fields {
slug
title
}
frontmatter {
title
date
}
}
}
}
}
`
/**
* Рендерим список постов со ссылками на них
*/
export default function BlogList(props) {
const { allMarkdownRemark } = props.data;
const { edges } = allMarkdownRemark;
return (
<div>
<h1>Blog List</h1>
{edges.map(({ node }) => {
const { slug, title } = node.fields;
const { date } = node.frontmatter;
return (
<div key={slug}>
<h2>
<Link to={`/${slug}/`}>{title}</Link>
</h2>
<p>{date}</p>
</div>
)
})}
</div>
)
}
src/templates/BlogPost.js
import React from 'react';
import { graphql } from 'gatsby';
/**
* Запрос для получения одного поста по его slug
* slug - это часть URL, которая идет после домена
* slug передается в компонент через контекст, который мы задали в
* gatsby-node.js (module.exports = { createPages })
*/
export const query = graphql`
query BlogPageQuery($slug: String!) {
markdownRemark(fields: {slug: {eq: $slug}}) {
html
}
site {
siteMetadata {
description
social {
siteUrl
twitter
twitterUsername
}
title
}
}
}
`
/**
* Рендерим пост, используя данные gql запроса
*/
export default function BlogPage(props) {
const { markdownRemark } = props.data;
const { html } = markdownRemark;
return <div dangerouslySetInnerHTML={{ __html: html }}></div>
}
src/pages/404.js
Ну и добавим сразу страничку для тех случаев, когда пользователь переходит на неизвестную для нас страницу - это будет статичная страница, просто кладем файлик
src/pages/404.js с таким содержимым (пока таким)
import React from "react"
export default function NotFoundPage() {
return (
<div>
<h1>404</h1>
<p>Page not found</p>
</div>
)
}На данный момент должна получиться следующая структура проекта: 
Проверяем отображение страниц
Запускаем npm run develop и если все хорошо, то мы должны увидеть на главной список из 5 постов, а если перейдем на /page/2/ то там должен быть список из другиз 5-и постов, а каждая ссылка должна открывать отдельный пост.
Вместо заключения
На данном этапе у нас есть основной функционал, внешний вид которого оставляет желать лучшего. Это и многое другое я буду добавлять в следующих статьях серии
Блог на Gatsby + Obsidian from scratch ч.2 Блог на Gatsby + Obsidian from scratch ч.3











