Как в Astro программно задать Layout для всех файлов Markdown в коллекции

22 декабря 2022 г.

Для вставки статьи, написанной c помощью Markdown, в шаблон страницы сайта на Astro, в документации предлагается указать Layout в секции Frontmatter файла Markdown:

post.md
---
layout: ../../layouts/Post.astro
title: Заголовок статьи
---

А в самом шаблоне страницы, посредством тега slot, необходимо указать то место, куда будет вставлен текст Markdown после рендеринга:

Post.astro
---
const {frontmatter} = Astro.props;
---
 
<html>
  <body>
    <h1>{frontmatter.title}</h1>
    <slot />
  </body>
</html>

Но у этого способа есть существенный недостаток - корректный Layout необходимо явно указать во всех файлах Markdown и, при этом, не ошибиться. А если таких файлов сотни или даже тысячи?

В Netlify CMS, при создании нового файла Markdown, эту процедуру можно автоматизировать, добавив скрытое поле со значением по умолчанию:

config.yml
collections:
  - name: 'posts'
    label: 'Блог'
    label_singular: 'Пост'
    folder: 'src/pages/posts'
    create: true
    fields:
      - { label: 'Шаблон', name: 'layout', widget: 'hidden', default: '../../layouts/Post.astro' }
      - { label: 'Заголовок', name: 'title', widget: 'string' }

Тем не менее, в случае изменения структуры сайта или при переносе файлов Markdown с другого ресурса, необходимо будет прописать новый Layout во всех этих файлах и это может превратиться в головную боль.

А можно не прописывать Layout в каждом файле Markdown, а указать его один раз для всех файлов Markdown в коллекции (папке)?

Да, можно.

Для этого необходимо воспользоваться динамической маршрутизацией и в этой статье я покажу как это сделать.

Первым делом переносим все файлы Markdown за пределы папки pages, чтобы Astro не генерировал для них страницы со статической маршрутизацией:

project folder
src
 ├ layouts
 │  └ ...
 ├ pages
 │  ├ posts
 │  │  └ [slug].astro
 │  └ ...
 └ posts
    ├ post.md
    ├ other.md
    └ ...

Внутри папки pages, в подпапке, где должны были располагаться файлы Markdown, создаём шаблон динамической маршрутизации [slug].astro со следующим содержимым:

[slug].astro
---
export async function getStaticPaths() {
  const posts = await Astro.glob("../../posts/*.md");
  return posts.map((post) => {
    const path = post.file.split("/");
    const slug = path[path.length - 1].split(".")[0];
    return {
      params: {
        slug: slug,
      },
      props: {
        post,
      },
    };
  });
}
 
const { frontmatter, Content } = Astro.props.post;
---
 
<html>
  <body>
    <h1>{frontmatter.title}</h1>
    <Content />
  </body>
</html>

Теперь, ко всей коллекции файлов Markdown, полученной при помощи функции Astro.glob(), будет применяться Layout динамической маршрутизации [slug].astro.

В этом примере переменная динамического маршрута [slug] заменяется на название файла Markdown без расширения. Но для этой цели можно использовать любое другое поле из Frontmatter. Например, можно создать поле slug для явного назначения адреса странице.