자신의 웹사이트에 dev.to 게시물 포함

저는 아주 최근에 제 dev.to - DEV 커뮤니티에 글을 올렸고 거기에서 제 dev.to 게시물을 제 웹사이트에 통합하는 것에 대해 언급했습니다. 그래서 간다!

아직 확인하지 않으셨다면 제 웹사이트는 SvelteKit 및 Tailwind CSS로 구축되었으며 여기에서 완전히 오픈 소스입니다: https://github.com/nunogois/nunogois-website

이 기능에 대한 기본 커밋here을 확인할 수 있지만 아래에서 중요한 부분을 분석해 보겠습니다.

API



우선 게시물을 가져와야 합니다. API 을 사용하면 매우 쉽습니다. "게시 날짜별로 정렬된 게시된 기사"를 반환하는 getLatestArticles 엔드포인트를 사용했습니다.

제 경우에는 다음과 같습니다.
GEThttps://dev.to/api/articles/latest?username=nunogois - 브라우저에서 이 URL에 액세스하여 테스트할 수 있습니다.
페이지 매김은 나중에 생각해야 할 것입니다.

어쨌든 이것을 내 웹사이트와 통합하기 위해 SvelteKit에서 내가 가장 좋아하는 기능 중 하나인 SvelteKit's endpoints을 활용했습니다. src/routes/api.ts에서 볼 수 있습니다.

// ...
export async function get(): Promise<EndpointOutput> {
    return {
        body: {
            // ...
            blog: await loadBlog()
        }
    }
}

export const loadBlog = async (): Promise<JSONString[]> =>
    await fetch('https://dev.to/api/articles/latest?username=nunogois').then((res) => res.json())


그런 다음 이 끝점은 내 index.svelte 구성 요소에 대한 소품으로 blog 배열을 전달하는 내 Blog 파일에서 가져옵니다.

<script context="module">
    export async function load({ fetch }) {
        // ...
        const res = await fetch('/api')

        if (res.ok) {
            return {
                props: await res.json()
            }
        }
    }
</script>

<script lang="ts">
    //...
    export let blog
</script>

<Blog {blog} />


블로그



Blog 구성 요소는 단일 페이지 웹 사이트의 섹션에 불과합니다. 여기서 관련된 부분은 src/pages/blog.svelte에서 볼 수 있는 각 블로그 게시물에 대해 무언가를 반복하고 렌더링하는 것입니다.

{#each filteredBlog as { slug, title, description, readable_publish_date, cover_image, tag_list, positive_reactions_count, comments_count, reading_time_minutes }}
  <div class="border border-light-gray rounded-xl">
    <a sveltekit:prefetch href={`/blog/${slug}`} class="flex flex-col h-full">
      <img src={cover_image} alt={title} class="w-full rounded-t-xl object-cover" />
      <h4 class="flex justify-center items-center text-lg font-medium p-2 border-light-gray">
        {title}
      </h4>
      <span class="text-xs text-gray-300 mb-1"
        >{readable_publish_date} - {reading_time_minutes} min read</span
      >
      <span class="text-xs text-gray-300">{tag_list.map((tag) => `#${tag}`).join(' ')}</span>
      <div class="text-xs my-3 mx-5 text-justify">
        {description}
      </div>
      <div class="flex-1 grid grid-cols-2 text-sm content-end">
        <div class="flex justify-center items-center border-t border-light-gray border-r p-1">
          <Icon icon="fa:heart" width="16px" class="inline-block mr-1" />
          {positive_reactions_count}
        </div>
        <div class="flex justify-center items-center border-t border-light-gray border-r p-1">
          <Icon icon="fa:comment" width="16px" class="inline-block mr-1" />
          {comments_count}
        </div>
      </div>
    </a>
  </div>
{/each}


이것은 모든 Tailwind CSS 클래스와 약간의 조정으로 인해 현재 약간 엉망이지만 지금은 정확히 내가 원하는 대로 보입니다. 곧 자체 구성 요소로 리팩토링해야 합니다(BlogItem 또는 이와 유사한 것).

이제 모든 블로그 게시물이 표시되었으므로 게시물을 열고 읽을 수 있는 방법이 필요합니다. 위의 앵커 태그를 확인하십시오.

<a sveltekit:prefetch href={`/blog/${slug}`}...

slug는 블로그 게시물을 고유하게 식별합니다.

강타



SvelteKit의 멋진 기능을 더 많이 활용하여 새src/routes/blog/[slug].svelte 파일을 만들었습니다.

<script context="module" lang="ts">
    // ...

    import Icon from '@iconify/svelte'

    export async function load({ page, fetch }) {
        const url = `https://dev.to/api/articles/nunogois/${page.params.slug}`
        const response = await fetch(url)

        return {
            status: response.status,
            props: {
                post: response.ok && (await response.json())
            }
        }
    }
</script>

<script lang="ts">
    export let post
</script>

<div class="flex justify-center">
    <div class="flex flex-col w-full px-4 md:px-24 max-w-screen-lg text-justify pt-16">
        <div class="border-b border-light-gray md:border md:rounded-xl">
            <img src={post.cover_image} alt={post.title} class="w-full rounded-t-xl object-cover mb-4" />
            <div class="md:px-4">
                <div class="flex">
                    <h3 class="w-full text-left text-2xl md:text-3xl font-medium">
                        {post.title}
                    </h3>
                    <a href={post.url} class="w-8"
                        ><Icon icon="fa-brands:dev" width="32px" class="inline-block" /></a
                    >
                </div>
                <div class="flex flex-col pt-2 pb-6 gap-1 text-xs text-gray-300">
                    <span>{post.readable_publish_date}</span>
                    <span>{post.tags.map((tag) => `#${tag}`).join(' ')}</span>
                </div>
                <div class="blog-post">
                    {@html post.body_html}
                </div>
            </div>
        </div>
        <a href={post.url} class="mt-5 text-center">React to this blog post on DEV Community 👩‍💻👨‍💻</a>
        <a href="/" class="my-5 text-center text-sm">www.nunogois.com</a>
    </div>
</div>


이것은 URL에서 slug를 가져오고 이를 사용하여 각 기사 끝점을 가져와 소품에 전달합니다. 그런 다음 원하는 대로 게시물을 렌더링하기만 하면 됩니다.

CSS



다음은 블로그 게시물과 포함된 콘텐츠를 올바르게 표시하기 위해 지금까지 추가한 특정 CSSsrc/app.css입니다.

.blog-post p {
  margin-bottom: 20px;
}

.blog-post > .crayons-card {
  border-width: 1px;
  --tw-border-opacity: 1;
  border-color: rgb(51 51 51 / var(--tw-border-opacity));
  border-radius: 0.75rem;
  margin-bottom: 20px;
}

.blog-post > .crayons-card > .c-embed__cover img {
  object-fit: cover;
  max-height: 200px;
  border-top-left-radius: 0.75rem;
  border-top-right-radius: 0.75rem;
}

.blog-post > .crayons-card > .c-embed__body {
  padding: 20px;
}

.blog-post > .crayons-card > .c-embed__body > h2 {
  margin-bottom: 8px;
  color: #93ceff;
}

.blog-post > .crayons-card > .c-embed__body > .truncate-at-3 {
  font-size: 0.875rem;
  margin-bottom: 8px;
}

.blog-post > .crayons-card > .c-embed__body > .color-secondary {
  font-size: 0.875rem;
}

.blog-post > .crayons-card .c-embed__favicon {
  max-height: 18px;
  width: auto;
  margin-right: 14px;
}


여기에서 어떻게 보이는지 확인할 수 있습니다. https://www.nunogois.com/blog/hello-world-4pdf

내가 그렇게 말하면 꽤 멋져 보입니다!

동적 sitemap.xml 및 rss.xml



보너스 라운드의 경우 동적 sitemap.xmlrss.xml를 설정해 보겠습니다.

참고: 여기에서는 배포 후 표시되도록 어떻게든 코드에서 엔드포인트를 참조해야 했기 때문에 index.svelte에서 가져오는 것입니다.

fetch('/sitemap.xml')
fetch('/rss.xml')


소스 파일은 다음과 같습니다.

사이트맵.xml



https://www.nunogois.com/sitemap.xml

다음은 src/routes/sitemap.xml.ts입니다.

import { loadBlog } from './api'

const website = 'https://www.nunogois.com'

export async function get(): Promise<unknown> {
    const posts = await loadBlog()
    const body = sitemap(posts)

    const headers = {
        'Cache-Control': 'max-age=0, s-maxage=3600',
        'Content-Type': 'application/xml'
    }
    return {
        headers,
        body
    }
}

const sitemap = (posts) => `<?xml version="1.0" encoding="UTF-8" ?>
<urlset
  xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
  xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"
  xmlns:xhtml="https://www.w3.org/1999/xhtml"
  xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"
  xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"
  xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"
>
  <url>
    <loc>${website}</loc>
    <changefreq>daily</changefreq>
    <priority>0.7</priority>
  </url>
  ${posts
        .map(
            (post) => `
  <url>
    <loc>${website}/blog/${post.slug}</loc>
    <changefreq>daily</changefreq>
    <priority>0.7</priority>
  </url>
  `
        )
        .join('')}
</urlset>`


rss.xml



https://www.nunogois.com/rss.xml

다음은 src/routes/rss.xml.ts입니다.

import { loadBlog } from './api'

const website = 'https://www.nunogois.com'

export async function get(): Promise<unknown> {
    const posts = await loadBlog()
    const body = xml(posts)

    const headers = {
        'Cache-Control': 'max-age=0, s-maxage=3600',
        'Content-Type': 'application/xml'
    }
    return {
        headers,
        body
    }
}

const xml = (
    posts
) => `<rss xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:atom="https://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Nuno Góis - Full-Stack Developer</title>
    <link>${website}</link>
    <description>Full-Stack Developer from Portugal. Experienced with every step of developing and delivering software projects using .NET C#, JavaScript, Go, Python, and more.</description>
    ${posts
            .map(
                (post) =>
                    `
        <item>
          <title>${post.title}</title>
          <description>${post.description}</description>
          <link>${website}/blog/${post.slug}/</link>
          <pubDate>${new Date(post.published_timestamp)}</pubDate>
          <content:encoded>${post.description} 
            <br />
            <a href="${website}/blog/${post.slug}">
              Read more
            </a>
          </content:encoded>
        </item>
      `
            )
            .join('')}
  </channel>
</rss>`


결론



도중에 몇 가지 수정 및 최적화를 수행하여 다음과 같은 Lighthouse 점수를 얻었습니다.









누노 고이스 👨🏻‍💻


@nunogois_dev






nunogois.com 몇 가지 업데이트와 새로운 dev.to 블로그 통합으로 Lighthouse에서 매우 산뜻해 보입니다 😎이 새로운 기능을 테스트하고 즐겨야 하므로 곧 몇 가지 사항을 게시해야 하므로 계속 지켜봐 주시기 바랍니다.


오후 22:14 - 2022년 6월 15일









이 통합은 확실히 완료되지 않았으며 이 게시물을 올바르게 표시하려면 이 게시물을 게시한 직후 몇 가지 추가 작업을 수행해야 할 것입니다. 그래도 꽤 재미있고 쉽게 할 수 있는 일이었습니다.

또한 웹 사이트 코드를 약간 리팩토링하고 정리하는 데 시간이 좀 걸릴 것이므로(모든 곳에 적절한 유형이 있어야 함) 계속 지켜봐 주시기 바랍니다.

내 웹사이트를 기반으로 자유롭게 웹사이트를 만들거나 영감을 받아보세요. 그렇다면 다음 문서를 살펴보는 것이 좋습니다.
  • https://kit.svelte.dev/docs/introduction
  • https://tailwindcss.com/docs/utility-first

  • 또한 공유해 주세요. 확인하고 싶습니다!

    좋은 웹페이지 즐겨찾기