@cazumbra

Как транслитировать на кириллицу динамический маршрут в NEXT JS?

У меня есть сайт на next.js с mdx. Там у меня есть статьи, у которых есть теги, они указаны во фронтметтере mdx файла статьи вот так
tags: ['functions', 'javascript', 'powerful code', 'js']
. Еще у меня есть маршрутизация по тегам — по пути pages/blog/tag/[tag].js, и создаются страницы, где собраны все статьи с соответствующими тегами. Ну в общем банальная вещь для блога.

Вот так выглядит моя служебная функция, которая создает все необходимое для генерации статей по нужным маршрутам:

import fs from 'fs';
import matter from 'gray-matter';

export function getAllPosts() {
    const files = fs.readdirSync('./content/posts');
    const posts = files
      .map((fileName) => {
        const slug = fileName.replace(/\.mdx$/, '');
        const { frontmatter } = getPostBySlug(slug);
        return {
          slug,
          ...frontmatter,
        };
      })

    return posts;
  }

export function getPostBySlug(slug) {
    const fileName = fs.readFileSync(`content/posts/${slug}.mdx`, 'utf-8');
    const { data: frontmatter, content } = matter(fileName);
    return {
      frontmatter,
      content,
    };
  }


export function getAllWork() {
    const data = fs.readFileSync('content/work/data.json', 'utf-8');
    const jsonData = JSON.parse(data);
    return jsonData.work;
}

export function getAllPostsByTag({ tag }) {
    const posts = getAllPosts();
    return posts.filter((post) => post.tags.includes(tag));
}


getAllPostsByTag в самом низу занимается по сути фильтрованием фронтметтера на предмет поиска тегов. Вот как выглядит файл pages/blog/tag/[tag].js:

import React, { useState, useEffect } from "react"
import {getAllPosts, getAllPostsByTag} from "@/lib/getAllData";
import Head from "next/head";
import Article from "@/components/article";


export async function getStaticPaths() {
    const posts = getAllPosts();
    const tags = new Set(posts.flatMap((post) => post.tags));

    return {
        paths: [...tags].map((tag) => {
            return {
                params: {
                    tag
                }
            }
        }),
        fallback: false,
    };
}

export async function getStaticProps({ params: { tag } }) {
    const posts = getAllPostsByTag({tag});

    return {
        props: {
            posts,
            tag
        },
    };
}

export default function Tag({ posts, tag }) {

    const numberPosts = 2

    // State for the list
    const [list, setList] = useState([...posts.slice(0, numberPosts)])

    // State to trigger oad more
    const [loadMore, setLoadMore] = useState(false)

    // State of whether there is more to load
    const [hasMore, setHasMore] = useState(posts.length > numberPosts)

    // Load more button click
    const handleLoadMore = () => {
        setLoadMore(true)
    }

    // Handle loading more articles
    useEffect(() => {
        if (loadMore && hasMore) {
        const currentLength = list.length
        const isMore = currentLength < posts.length
        const nextResults = isMore
            ? posts.slice(currentLength, currentLength + numberPosts)
            : []
        setList([...list, ...nextResults])
        setLoadMore(false)
        }
    }, [loadMore, hasMore]) //eslint-disable-line

    //Check if there is more
    useEffect(() => {
        const isMore = list.length < posts.length
        setHasMore(isMore)
    }, [list]) //eslint-disable-line


    return (
        <div>
            <Head>
                <title>NextJS Blog</title>
                <meta name="description" content="Generated by create next app" />
                <link rel="icon" href="/public/favicon.ico" />
            </Head>
            <section className='px-6'>
                <div className='max-w-4xl mx-auto'>
                    <h1 className='text-3xl font-bold mb-6 p-4'>All `{tag}` posts</h1>
                    {list.map((post) => (
                        <Article key={post.slug} className='border-b-2' post={post} />
                    ))}
                </div>
                {hasMore ? (
                <div><button onClick={handleLoadMore}>Еще статьи</button></div>
                ) : (
                    <div><button disabled>Больше нет статей</button></div>
                )}
            </section>
        </div>
    )
}


Дело в том, что маршруты создаются нормально, когда мой тег во фронтметтере моего .mdx файла написан на английском языке (латинскими буквами) и одним словом (например, ‘javascript’ или ‘nextjs’), но если мой тег написан на Кириллице и/или в два слова (например, 'мощный код' или 'super code'), то никакие маршруты не генерируются корректно.

В моем компоненте, который отображает статьи в списке статей везде, где этот список используется, я могу использовать свои собственные функции, такие как «транслеттер» и Lodash, которые транслитируют кириллицу и делают кебаб из тегов, которые состоят из нескольких слов. С помощью этих опций я могу менять url под каждым тегом, но самих страниц нет:

// компонент статьи
import Link from 'next/link'
import Date from '@/lib/date';
import { transliterate } from '@/lib/transletter';
const _ = require("lodash")

const getTagLink = (tag) => {

    return (
      <Link href={`/blog/tag/${_.kebabCase(transliterate(tag))}`} key={tag}>
       {tag}
      </Link>
    );
  };

export default function Article({ post }) {
    return (
        <article className={`bg-white p-4`}>
            <Link href={`/blog/${post.slug}`}>
                <h3 className='text-2xl mb-2 font-medium hover:text-red-400 cursor-pointer'>
                {post.title}
                </h3>
            </Link>
            <span className='text-gray-600 mb-4 block'>
                <Date dateString={post.date} /> | {post.tags.map(tag => getTagLink(tag)).reduce((prev, curr) => [prev, ', ', curr])}
            </span>
            <p>{post.description}</p>
        </article>
    );
}


Этот самый${_.kebabCase(transliterate(tag))} все делает правильно - транслитерирует кириллические символы для url и добавляет дефис между словами, то есть вместо blog/tag/мощный кодполучается blog/tag/moschnyi-kod. Однако у меня нет такого маршрута, потому что [tag].js принимает имя тега буквально — как оно написано.

Как мне добавить что-то вроде _.kebabCase(transliterate(tag)) в мои функции getStaticPath или еще куда-то, чтобы все работало как надо, чтобы генерировался нужный динамический маршрут, где страница тега будет получать url с переведенным на латиницу именем тега и дефисами между словами? Возможно это вообще?
  • Вопрос задан
  • 106 просмотров
Пригласить эксперта
Ответы на вопрос 1
@cazumbra Автор вопроса
Огромное количество ответов. Видимо криво задан вопрос))) Поковырялся тут и вроде что-то нашел) Если в файле [tag].js прописать функции getStaticPAths вот так:

export async function getStaticPaths() {
    const posts = getAllPosts();
    const tags = new Set(posts.flatMap((post) => post.tags));

    return {
        paths: [...tags].map((tag) => {
            return {
                params: {
                    tag:  _.kebabCase( transliterate( tag ) )
                }
            }
        }),
        fallback: false,
    };
}


то все вроде работает, то есть создается страница с обработанным тегом (с латиницей вместо кириллицы и дефисами между словами), но теперь получается обратный эффект - nextjs ищет именно этот тег в моем фронтметтере ( у статьи написан тег "мощный код" , а фильтрация идет по "moschnyi-kod"). Страница с тегом теперь показывается, но без статей, хоть и не 404. Что теперь хз...
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы