Dev.to를 내 다음 CMS로 사용합니다.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_url
frontmatter 속성을 사용하여 제 블로그에서 이 글과 슬로프가 무엇인지 보고 싶다는 뜻을 나타냅니다.제 블로그에 발표하고 싶은 모든 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 F가 Unsplash
Reference
이 문제에 관하여(Dev.to를 내 다음 CMS로 사용합니다.js 블로그), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/juliang/using-dev-to-as-cms-for-my-next-js-blog-1aan텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)