Dev.to를 내 다음 CMS로 사용합니다.js 블로그

25632 단어 ssrreactdevtonextjs
나의 Next.js 사이트는 사용한다.이것은 구축할 때 모든 페이지를 생성하고 요청할 때마다 단독으로 생성합니다.새로운 글도 내 블로그에 나올 수 있고 구축과 배치가 필요 없다.다음은 그것의 작업 원리다.

I'll assume the reader is somewhat familiar with Data Fetching in Next.js.


내 블로그에는 두 개의 페이지가 있는데 하나는 글 목록(/pages/blog.ts, 다른 하나는 개인 글(/pages/[slug].ts, 사용dynamic routing이다.
전체 소스 코드에 대해서는 this repository를 볼 수 있습니다.

기사 목록

blog.ts 페이지 내보내기 getStaticProps 함수.이것은 나의 모든 글을 dev.to에서 사용할 수 있도록 합니다.그리고 목록을 페이지 구성 요소에 전달하고 보여 줍니다.

글을 거르다


어떤 이유 때문에, 나는 내가 발표한 모든 dev.to 문장이 나의 블로그에 나타나는 것을 원하지 않는다.
또 하나 해결해야 할'문제'는 slugs이다.Dev.to는 제목을 사용하고 임의 문자를 추가하여 slug를 자동으로 생성합니다.내 자신의 사이트에서 나는 자신의 콧물벌레를 선택할 수 있기를 바란다.
나는 이 두 문제를 해결할 방법을 찾았다.canonical_urlfrontmatter 속성을 사용하여 제 블로그에서 이 글과 슬로프가 무엇인지 보고 싶다는 뜻을 나타냅니다.
제 블로그에 발표하고 싶은 모든 dev.to 글에 대해 저는 제 사이트 URL으로 시작하는 cononical_url를 지정합니다.예를 들어 내가 있는 블로그 게시물은 하나 있다canonical_url: https://juliangaramendy.dev/blog/react-state-management-2020.
전체 목록을 가져오면, 게시되고 유효한 canonical_url 문장만 선별됩니다.
또 다른 장점은 dev.to가 좋은'최초 발표juliangaramendy.dev'메시지를 표시하고 내 사이트에 있는 글을 가리키는 링크를 가지고 있다는 것이다.

문장 페이지


단일 기사의 경우 /[slug].ts 페이지가 생성됩니다.이 모듈은 또한 getStaticProps 함수를 내보냅니다. 이 함수는 문장을 되돌려 도구로 React 구성 요소에 보내고 나타냅니다.
그러나 dev.to API에서 별도의 기사를 가져올 수는 없습니다slug는 다르기 때문입니다.그래서 못 찾겠어요.
https://juliangaramendy.dev/blog/
react-state-management-in-2020-3c58
react-state-management-2020
내가 할 일은: 전체 목록을 다시 가져와서 canonical_url 속성이 slug 파라미터와 일치하는 글을 찾은 다음 되돌려줍니다./[slug].ts 모듈은 구축할 때 미리 생성된 플러그 목록을 되돌려 주는 getStaticPaths 함수를 내보냅니다.
이를 위해, 나는 모든 목록을 다시 가져와 모든 슬로프를 되돌려줍니다.
export async function getStaticPaths() {
  const posts = await getAllPosts()
  const paths = posts.map((post) => ({ params: { slug: post.slug } }))
  return { paths, fallback: true }
}
구축할 때 존재하지 않는 새로운 글을 요청할 때 내 블로그에서 찾을 수 있도록 설정 fallback: true 했습니다.

기사 페이지 재생성


정적 도구로 돌아갈 때 페이지가 1초 후에 revalidate 속성을 사용하여 재생성될 수 있음을 지적합니다.
export async function getStaticProps(context) {
  const slug = context.params?.slug
  const post = await getPostBySlug(`${slug}`)
  return post ? { props: { post }, revalidate: 1 } : { notFound: true }
}
넥스트를 이용했다.jsIncremental Static Regeneration

매번 다시 가져오는 것을 피하다


위의 실현은 좀 유치하다.
다음.js 구축 과정은 함수를 호출하여 몇 초 안에 글 목록을 여러 번 가져옵니다.dev.to API는 사용 제한이 있기 때문에 불필요할 뿐만 아니라 문제가 있습니다.

메모리에 네트워크 요청 캐시


커다란 개선은fetch 호출을 정적 캐시에 봉인하는 것이다. 그러면 후속 호출을 피하고 캐시를 되돌릴 수 있다.

캐시가 기한이 지나야 합니다. 그렇지 않으면 블로그에서 새 글을 '가져올 수 없습니다.나는 1분으로 설정했지만, 장래에는 10분으로 연장될 수도 있다.나는 사람들이 10분을 기다려서 나의 댓글을 읽을 수 있을 것이라고 믿는다.
이것은one implementation입니다.
// cache.ts

type CacheEntry = {
  expiresAt: number
  value: unknown
}

const cacheMap: Record<string, CacheEntry> = {}

const EXPIRATION = 1000 * 60 * 1 // 1 minute

export async function getCached<V>(key: string, fn: () => Promise<V>): Promise<V> {
  if (shouldRevalidate(key)) {
    await revalidateKey(key, fn)
  }
  return cacheMap[key].value as V
}

function shouldRevalidate(key: string): boolean {
  return cacheMap[key] ? new Date().getTime() > cacheMap[key].expiresAt : true
}

async function revalidateKey<V>(key: string, fn: () => Promise<V>) {
  const response = await fn()
  cacheMap[key] = {
    value: response,
    expiresAt: new Date().getTime() + EXPIRATION,
  }
  return cacheMap[key].value as V
}
다음과 같이 사용합니다.
function fetchAllDevArticles(): Array<Article> {
  return fetch('https://dev.to/api/articles/me/published', { 
    headers: { 'api-key': process.env.DEVTO_API_KEY || '' },
  }).then((r) => r.json())
}

async function getAllDevArticles() {
  const articles = await getCached('dev.to/articles', fetchAllDevArticles)
  return articles.filter(article => !!article.canonical_url)
}
호출 getAllDevArticles 할 때마다 응답을 받거나 캐시 값을 받습니다.물론 처음 값을 가져오면 캐시가 여러 번 사용되고 다음 요청이 만료되면 API 요청이 다시 생성됩니다.
하지만 아직 충분하지 않다.나의 경험에 따르면, 이것은 단지 어느 때에만 적용된다.
컴퓨터에서 로컬로 구축할 때, 4개의 병렬 노드 프로세스가 실행 중인 것을 볼 수 있다.이것은 4개의 네트워크 요청을 초래할 수 있으며, 통상적으로 가능하다.
그러나 이 프로세스가 Vercel에 구축되면 일반적으로 8개의 동시 노드 프로세스가 발생하고 dev.to API에 오류가 발생합니다.
❌ 429 Too Many Requests
이 문제를 해결하는 방법의 하나는 캐시를 디스크에 저장하는 것이다. 이렇게 하면 여러 프로세스가 이익을 얻을 수 있다.

디스크에 네트워크 요청 캐시



이것은 효과가 있다. 왜냐하면 다음 것이다.jsbuild에서 처음으로 네트워크 요청을 터치한 다음 응답을 디스크에 저장합니다.
다음에 어떤 프로세스를 통해 요청을 할 때 먼저 디스크 캐시를 시도합니다.존재하고 기한이 지나지 않으면 그것을 사용합니다. 그렇지 않으면 다시 요청을 하고 저장합니다.
다음은 one implementation입니다.
import sha from 'sha-1'
import os from 'os'
import { join } from 'path'
import { readJsonFile, writeJsonFile } from './fs-read-write-json'

const getTempPath = (path: string) => join(os.tmpdir(), path)

type CacheEntry<V> =
  | {
      status: 'done'
      expiresAt: number
      value: V
    }
  | {
      status: 'pending'
      expiresAt: number
    }

export function getCached<V>(
  key: string,
  fn: () => Promise<V>,
  cacheDurationMs: number
) {
  const tempFilePath = getTempPath(`./jgdev-cache-${sha(key)}.json`)

  const getEntry = async () =>
    readJsonFile<CacheEntry<V> | undefined>(tempFilePath)

  const writePendingEntry = () =>
    writeJsonFile(tempFilePath, {
      status: 'pending',
      expiresAt: new Date().getTime() + 10000,
    })

  const writeEntry = async (value: V) =>
    writeJsonFile(tempFilePath, {
      status: 'done',
      expiresAt: new Date().getTime() + cacheDurationMs,
      value,
    })

  const wait = (t: number) => new Promise((resolve) => setTimeout(resolve, t))

  const revalidate = async (): Promise<V> => {
    await writePendingEntry()
    const value = await fn()
    await writeEntry(value)
    return value
  }

  const hasExpired = (entry: CacheEntry<V>) =>
    new Date().getTime() > entry.expiresAt

  const getValue = async (): Promise<V> => {
    const entry = await getEntry()
    if (entry) {
      if (entry.status === 'done' && !hasExpired(entry)) {
        return entry.value
      } else if (entry.status === 'pending' && !hasExpired(entry)) {
        await wait(500)
        return getValue()
      }
    }
    return revalidate()
  }

  return getValue()
}
궁금하시면 여기read/write module입니다.
Vercel에 배치하면 os.tmpdir() 을 사용하는 것이 매우 중요하다. 왜냐하면 우리는 다른 어느 곳에서도 파일 시스템에 쓸 수 없기 때문이다.
❌ Error: EROFS: read-only file system
전체 소스 코드에 대해서는 this repository를 볼 수 있습니다.
당신의 생각을 평론에서 저에게 알려 주세요.캐시 모듈을 교체할 수 있는 라이브러리가 있는지 알고 싶습니다.
사진 작성자Fitore FUnsplash

좋은 웹페이지 즐겨찾기