将 dev.to 帖子嵌入到您自己的网站

2025-06-10

将 dev.to 帖子嵌入到您自己的网站

我最近在 dev.to - DEV 社区上发表了我的第一篇文章,其中提到了将我的 dev.to 文章整合到我自己的网站中。现在就开始吧!

如果你还没有看过,我的网站是用 SvelteKit 和 Tailwind CSS 构建的,它在这里完全开源:https://github.com/nunogois/nunogois-website

您可以在此处查看此功能的主要提交,但我会尝试分解下面的重要部分。

API

首先,我们需要获取帖子,使用API很容易做到。我使用了getLatestArticles端点,它返回“按发布日期排序的已发布文章”。

就我而言,它看起来像这样:
GET https://dev.to/api/articles/latest?username=nunogois - 你可以在浏览器中访问此 URL 进行测试。
分页是我稍后需要考虑的事情。

无论如何,为了将其与我的网站集成,我利用了SvelteKit 的端点,这是我最喜欢的 SvelteKit 功能之一,您可以在其中看到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())
Enter fullscreen mode Exit fullscreen mode

然后,这个端点被提取到我的index.svelte文件中,并将blog数组作为 prop 传递给我的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} />
Enter fullscreen mode Exit fullscreen mode

博客

我的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}
Enter fullscreen mode Exit fullscreen mode

目前有点乱,充斥着各种 Tailwind CSS 类和一些小调整,但目前看起来已经完全符合我的要求了。可能很快会把它重构成一个独立的组件(BlogItem或者类似的东西)。

现在我们已经显示了所有博客文章,我们需要一种打开和阅读它们的方法。注意上面的锚标签:

<a sveltekit:prefetch href={`/blog/${slug}`}...
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

这段代码slug从 URL 中获取了相应的文章端点,并将其传递给 props。之后,我们只需要按照自己的意愿渲染文章即可。

CSS

以下是我目前添加的具体 CSS,src/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;
}
Enter fullscreen mode Exit fullscreen mode

您可以在此处看到它的样子:https://www.nunogois.com/blog/hello-world-4pdf

看起来非常漂亮,如果我这么说的话!

动态 sitemap.xml 和 rss.xml

为了获得奖励回合,让我们设置一个动态sitemap.xmlrss.xml

注意:在这里我必须以某种方式在代码中引用它们的端点,以便它们在部署后显示出来,这就是我获取它们的原因index.svelte

fetch('/sitemap.xml')
fetch('/rss.xml')
Enter fullscreen mode Exit fullscreen mode

源文件如下所示:

站点地图.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>`
Enter fullscreen mode Exit fullscreen mode

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>`
Enter fullscreen mode Exit fullscreen mode

结论

在此过程中,我还做了一些修正和优化,最终得到了这个 Lighthouse 分数:

这个集成肯定还没完成,我确信在发布这篇文章后我得再做一些额外的工作才能正确显示它。不过,这是一件相当有趣且轻松的事情。

我可能还应该花一些时间来重构和清理我的网站代码(并在各处使用适当的类型),所以请继续关注。

您可以根据我的设计随意搭建自己的网站,或者从中汲取一些灵感。如果您愿意,我建议您查看以下文档:

另外,请分享,我很想看看!

鏂囩珷鏉ユ簮锛�https://dev.to/nunogois/embed-devto-posts-into-your-own-website-36jj
PREV
解决复杂编码问题的 7 个强大原则 😤 长评论 🥴
NEXT
您的第一个使用 React 和 React-Spring 的响应式动画导航栏