Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 32 additions & 16 deletions app/components/BlogPostFederatedArticles.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
<script setup lang="ts">
import type { FederatedArticleInput } from '#shared/types/blog-post'
import type { Author } from '#shared/schemas/blog'

const props = defineProps<{
headline: string
articles: FederatedArticleInput[]
}>()

const contentKey = computed(() => props.articles.map(a => a.url).join('-'))

const { data: federatedArticles, status } = await useAsyncData(
`federated-articles-${contentKey.value}`,
() => useFederatedArticles(props.articles),
{
watch: [() => props.articles],
default: () => [],
},
// Prepare authors list for the composable
const authors = computed<Author[]>(() =>
props.articles.map(article => ({
name: article.authorHandle,
blueskyHandle: article.authorHandle,
})),
)

// Fetch author profiles for avatars
const { resolvedAuthors } = useBlueskyAuthorProfiles(authors.value)

// Merge the input data with the fetched avatars
const federatedArticles = computed(() => {
return props.articles.map((article, index) => {
const profile = resolvedAuthors.value[index]

return {
url: article.url,
title: article.title,
description: article.description,
authorHandle: article.authorHandle,
author: {
name: profile?.name || article.authorHandle,
blueskyHandle: article.authorHandle,
avatar: profile?.avatar || null,
profileUrl: profile?.profileUrl || null,
},
}
})
})
</script>

<template>
Expand All @@ -24,7 +45,7 @@ const { data: federatedArticles, status } = await useAsyncData(
{{ headline }}
</h2>
<section
v-if="federatedArticles?.length"
v-if="federatedArticles.length"
class="grid gap-4 grid-cols-[repeat(auto-fit,minmax(14rem,1fr))] transition-[grid-template-cols]"
>
<a
Expand All @@ -35,12 +56,7 @@ const { data: federatedArticles, status } = await useAsyncData(
:key="article.url"
class="grid grid-cols-[auto_1fr] gap-x-5 no-underline hover:no-underline rounded-lg border border-border p-4 transition-shadow hover:shadow-lg hover:shadow-gray-500/50"
>
<AuthorAvatar
v-if="article?.author"
:author="article.author"
size="md"
class="row-span-2"
/>
<AuthorAvatar v-if="article.author" :author="article.author" size="md" class="row-span-2" />
<div class="flex flex-col">
<p class="text-lg text-fg leading-tight m-0">
{{ article.title }}
Expand Down
67 changes: 0 additions & 67 deletions app/composables/useFederatedArticles.ts

This file was deleted.

11 changes: 10 additions & 1 deletion app/pages/blog/alpha-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extension, everyone who works in the JavaScript ecosystem."
date: "2026-03-03"
slug: "alpha-release"
description: "npmx is an open-source project built by a rapidly growing community. It's for open-source developers, and by extension, everyone who works in the JavaScript ecosystem."
draft: false
draft: true
---

# Announcing npmx: a fast, modern browser for the npm registry
Expand Down Expand Up @@ -118,46 +118,55 @@ headline="Read more from the community"
:articles="[
{
url: 'https://whitep4nth3r.com/blog/how-to-make-your-first-open-source-contribution/',
title: 'How to Make Your First Open Source Contribution',
authorHandle: 'whitep4nth3r.com',
description: 'Getting involved in open source doesn\'t have to be scary! Understand how to find a great project and make your first contribution in this guide from Salma.'
},
{
url: 'https://graphieros.github.io/graphieros-blog/blog/2026/npmx.html',
title: 'vue-data-ui is on npmx npmx is on vue-data-ui',
authorHandle: 'graphieros.com',
description: 'Graphieros explores a minimal npm-based workflow and why it exists.'
},
{
url: 'https://www.alexdln.com/blog/npmx-the-month',
title: 'The month. npmx',
authorHandle: 'alexdln.com',
description: 'Alex reflects on the project, warm stories, wonderful people, and a look into the future'
},
{
url: 'https://johnnyreilly.com/npmx-with-a-little-help-from-my-friends',
title: 'npmx: With a Little Help From My Friends',
authorHandle: 'johnnyreilly.com',
description: 'How to contribute to npmx.dev, and thoughts on Johnny\'s experience with the project.'
},
{
url: 'https://blog.trueberryless.org/blog/npmx/',
title: 'PLACEHOLDER_TITLE',
Comment thread
whitep4nth3r marked this conversation as resolved.
Outdated
authorHandle: 'trueberryless.org',
description: 'Telling the story of a newly founded community.'
},
{
url: 'https://www.sybers.fr/blog/3mfhn5xoawz24',
title: 'From a Bluesky post to my favorite open source community',
authorHandle: 'sybers.fr',
description: 'The best open source projects aren\'t just about great code. They\'re about the people behind them.'
},
{
url: 'https://storybook.js.org/blog/storybook-npmx',
title: 'PLACEHOLDER_TITLE',
Comment thread
whitep4nth3r marked this conversation as resolved.
Outdated
authorHandle: 'storybook.js.org',
description: 'We\'re huge fans of what the npmx community is building. Today\'s alpha is just the starting line, and we\'re proud to be running alongside them.'
},
{
url: 'https://jensroemer.com/writing/open-source-whats-in-it-for-me/',
title: 'Open source, what\'s in it for me?',
authorHandle: 'jensroemer.com',
description: 'Reflections on learning, community, and change.'
},
{
url: 'https://paulie.codes/blog/3mfs2stugzp2v',
title: 'Overcoming Imposter Syndrome: My First Open Source Contribution',
authorHandle: 'paulie.codes',
description: 'The most important part of open source is the people, and everyone has something valuable to bring to the table.'
}
Expand Down
2 changes: 1 addition & 1 deletion nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export default defineNuxtConfig({
'/settings': { prerender: true },
'/recharging': { prerender: true },
// proxy for insights
'/blog/**': { isr: true, prerender: true },
'/blog/**': { prerender: true },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good benefit of removing the scraping!

'/_v/script.js': {
proxy: 'https://npmx.dev/_vercel/insights/script.js',
},
Expand Down
87 changes: 0 additions & 87 deletions server/api/atproto/blog-meta.get.ts

This file was deleted.

11 changes: 2 additions & 9 deletions shared/types/blog-post.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type * as app from '#shared/types/lexicons/app'
import type { BlogMetaResponse } from '#shared/schemas/atproto'
import type { ResolvedAuthor } from '#shared/schemas/blog'

export type CommentEmbed =
| { type: 'images'; images: app.bsky.embed.images.ViewImage[] }
Expand All @@ -24,16 +22,11 @@ export interface Comment {
WARN: FederatedArticleInput specifics
interface - All strings must be captured in single quotes in order to be parsed correctly in the MD file
authorHandle - Must not contain `@` symbol prefix
description - Passing an empty string `''` will fallback to the description provided by the scraped meta tags
description - Any additional single quotes must be properly escaped with a `\`
*/
export interface FederatedArticleInput {
url: string
title: string
authorHandle: string
description?: string
}

export type ResolvedFederatedArticle = Omit<BlogMetaResponse, 'author' | '_meta'> & {
url: string
author: ResolvedAuthor
description: string
}
Loading