npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

apollo-flavor

v2.0.0

Published

Apollo Client JSX components that eliminate Suspense 2-depth problem

Readme

apollo-flavor

해당 라이브러리는 Toss/Suspensive의 모티브를 받아 Apollo Client와 GraphQL을 사용하는 개발자들을 위해 만들어졌습니다. Apollo Client를 위한 JSX 컴포넌트 라이브러리로, Suspense 2Depth 문제를 해결하여 Suspense와 비동기 데이터 페칭을 같은 컴포넌트 depth에서 처리할 수 있게 해줍니다.

📦 설치

npm install apollo-flavor
# or
pnpm add apollo-flavor
# or
yarn add apollo-flavor

문제: useSuspenseQuery의 Suspense 2Depth 문제

// ❌ 기존 방식: Suspense와 데이터 페칭이 분리된 depth
function UserPage({ userId }) {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile userId={userId} /> {/* 별도 컴포넌트 필요 */}
      <UserPosts userId={userId} /> {/* 별도 컴포넌트 필요 */}
    </Suspense>
  );
}

// 데이터 페칭을 위한 추가 컴포넌트들
function UserProfile({ userId }) {
  const { data } = useSuspenseQuery(GET_USER, { variables: { userId } });
  return <div>{data.user.name}</div>;
}

function UserPosts({ userId }) {
  const { data } = useSuspenseQuery(GET_POSTS, { variables: { userId } });
  return data.posts.map((post) => <PostItem key={post.id} post={post} />);
}

apollo-flavor: Suspense 2Depth 없는 같은 depth 처리

// ✅ apollo-flavor: Suspense와 데이터 페칭이 같은 depth에서 처리
function UserPage({ userId }) {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <SuspenseQuery query={GET_USER} variables={{ userId }}>
        {({ data }) => <div>{data.user.name}</div>}
      </SuspenseQuery>

      <SuspenseQuery query={GET_POSTS} variables={{ userId }}>
        {({ data }) =>
          data.posts.map((post) => <PostItem key={post.id} post={post} />)
        }
      </SuspenseQuery>
    </Suspense>
  );
}

🎨 사용법

SuspenseQuery: Suspense 2Depth 없는 쿼리 컴포넌트

기본 사용법

import { SuspenseQuery } from "apollo-flavor";
import { gql } from "@apollo/client";

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
      avatar
    }
  }
`;

function UserProfile({ userId }) {
  return (
    <Suspense fallback={<UserSkeleton />}>
      <SuspenseQuery query={GET_USER} variables={{ id: userId }}>
        {({ data, refetch, networkStatus }) => (
          <div className="user-profile">
            <img src={data.user.avatar} alt={data.user.name} />
            <h1>{data.user.name}</h1>
            <p>{data.user.email}</p>
            <button onClick={() => refetch()}>새로고침</button>
          </div>
        )}
      </SuspenseQuery>
    </Suspense>
  );
}

SuspenseFragment: Suspense 2Depth 없는 프래그먼트 컴포넌트

@apollo/client/useSuspenseFragment

GraphQL Fragment를 같은 depth에서 처리할 수 있습니다.

기존의 useSuspenseFragment의 사용사례 입니다.
import { SuspenseFragment } from "apollo-flavor";
import { gql } from "@apollo/client";

const USER_CARD_FRAGMENT = gql`
  fragment UserCard on User {
    id
    name
    avatar
    role
  }
`;

// 재사용 가능한 컴포넌트
function UserCard({ user }) {
  const { data } = useSuspenseFragment({
    fragment: USER_CARD_FRAGMENT,
    from: user,
  });

  return (
    <div className="user-card">
      <img src={data.avatar} alt={data.name} />
      <h3>{data.name}</h3>
      <span>{data.role}</span>
    </div>
  );
}

// 여러 쿼리에서 같은 컴포넌트 재사용
const GET_POST_WITH_AUTHOR = gql`
  query GetPost($id: ID!) {
    post(id: $id) {
      title
      content
      author {
        ...UserCard # 같은 Fragment 재사용
      }
    }
  }
  ${USER_CARD_FRAGMENT}
`;

const GET_COMMENTS = gql`
  query GetComments($postId: ID!) {
    comments(postId: $postId) {
      id
      content
      author {
        ...UserCard # 같은 Fragment 재사용
      }
    }
  }
  ${USER_CARD_FRAGMENT}
`;

// 두 곳에서 같은 UserCard 컴포넌트 사용
function PostPage() {
  const { data: post } = useSuspenseQuery(GET_POST_WITH_AUTHOR);
  const { data: comments } = useSuspenseQuery(GET_COMMENTS);

  return (
    <div>
      <UserCard user={post.author} /> {/* 재사용 */}
      {comments.map((comment) => (
        <div key={comment.id}>
          <p>{comment.content}</p>
          <UserCard user={comment.author} /> {/* 재사용 */}
        </div>
      ))}
    </div>
  );
}

apollo-flavor에선?

function PostPage({ postId }) {
  return (
    <div className="post-page">
      <Suspense fallback={<PostSkeleton />}>
        <SuspenseQuery query={GET_POST_WITH_AUTHOR} variables={{ id: postId }}>
          {({ data: post }) => (
            <article>
              <h1>{post.title}</h1>
              <div className="post-content">{post.content}</div>

              <div className="author-section">
                <h3>작성자</h3>
                {/* SuspenseFragment를 SuspenseQuery와 같은 레벨에서 사용 */}
                <SuspenseFragment
                  fragment={USER_CARD_FRAGMENT}
                  fragmentName="UserCard"
                  from={post.author}
                >
                  {({ data }) => <UserCard user={data} />}
                </SuspenseFragment>
              </div>
            </article>
          )}
        </SuspenseQuery>
      </Suspense>

      <Suspense fallback={<CommentsSkeleton />}>
        <SuspenseQuery query={GET_COMMENTS} variables={{ postId }}>
          {({ data: comments }) => (
            <section className="comments">
              <h3>댓글 ({comments.length})</h3>
              {comments.map((comment) => (
                <div key={comment.id} className="comment">
                  <p>{comment.content}</p>

                  <div className="comment-author">
                    {/* 각 댓글의 작성자도 같은 패턴으로 */}
                    <SuspenseFragment
                      fragment={USER_CARD_FRAGMENT}
                      fragmentName="UserCard"
                      from={comment.author}
                    >
                      {({ data }) => <UserCard user={data} />}
                    </SuspenseFragment>
                  </div>
                </div>
              ))}
            </section>
          )}
        </SuspenseQuery>
      </Suspense>
    </div>
  );
}

📄 라이선스

MIT License