[번역] Next.js 13.4

Saetbyeol·2023년 5월 23일
37

translations.zip

목록 보기
5/13
post-thumbnail

원문: Next.js 13.4

작성자: Tim Neutkens (@timneutkens), Sebastian Markbåge (@sebmarkbage)

Next.js 13.4 버전이 릴리즈 되었으며 이제 App 라우터 기능이 안정화 단계에 도달했음을 알립니다.

6개월 전 Next.js 13의 릴리즈 이후, 우리는 Next.js의 미래인 App 라우터를 불필요한 변경 사항 없이 점진적으로 도입하기 위한 기반을 구축하는 것에 집중했습니다.

오늘 13.4 버전을 릴리즈와 함께 여러분의 프로덕션 환경에서 App 라우터를 사용할 수 있습니다.

npm i next@latest react@latest react-dom@latest eslint-config-next@latest

Next.js App 라우터

2016년 리액트 애플리케이션의 서버 렌더링을 쉽게 제공하고자 Next.js를 릴리즈했습니다. 우리의 목표는 보다 동적이며 개인화되고 더욱 포괄적인 웹을 만드는 것이었습니다.

원래의 안내문에서 Next.js의 몇 가지 설계 원칙을 공유했습니다.

  • 제로 셋업. 파일 시스템을 API로 사용하기
  • 자바스크립트로만. 모든 것은 함수
  • 자동 서버 렌더링 및 코드 분할
  • 데이터를 가져오는 것은 개발자의 몫

Next.js는 이제 여섯 살이 되었습니다. 우리가 처음에 가졌던 설계 원칙은 여전히 유지되고 있으며, 많은 개발자들과 회사가 Next.js를 선택하고 있습니다. 이러한 원칙들을 더 잘 달성하고자 프레임워크의 근본적인 개선을 위해 노력해 왔습니다.

차세대 Next.js에 대해 연구해 왔고, 오늘 발표된 13.4는 안정적이며 더 많은 사람들과 만날 준비를 마쳤습니다. 이 글에서는 App 라우터를 위한 설계상의 결정사항과 선택사항에 대해 공유하고자 합니다.

제로 셋업. 파일 시스템을 API로 사용하기

파일시스템 기반의 라우팅은 Next.js의 핵심 기능입니다. 원래 글에서, 단일 리액트 컴포넌트에서 라우트를 생성하는 예제를 보여드렸습니다.

// Pages 라우터
// pages/about.js

import React from 'react';
export default () => <h1>About us</h1>;

더 추가로 구성해야 하는 건 없습니다. 그저 파일을 pages/ 하에 두기만 하면 Next.js 라우터가 알아서 남은 일을 처리합니다. 우리는 여전히 이런 라우팅의 단순함을 좋아합니다. 하지만 프레임워크의 사용량이 증가하면서, 개발자가 구축하고자 하는 인터페이스의 유형 또한 다양해졌습니다.

개발자들은 레이아웃을 정의하는 것, 레이아웃으로써의 UI 조각들을 중첩하는 것, 로딩 및 에러 상태를 정의하는 것에 대한 더 많은 유연성 제공과 같은 개선을 요청했습니다. 기존 Next.js 라우터에 이러한 기능을 추가하는 건 몹시 어려웠습니다.

프레임워크의 모든 부분은 라우터를 중심으로 설계되어야 합니다. 페이지 전환, 데이터 페칭, 데이터 변경과 재검증, 스트리밍, 스타일링 등이 이에 해당합니다.

스트리밍과 호환되는 라우터를 만들고, 레이아웃에 대해 강화된 지원에 대한 요청을 해결하기 위해 우리는 새로운 버전의 라우터를 개발하는 데 착수했습니다.

레이아웃 RFC의 초기 릴리즈 이후 바로 이 지점에 도달했습니다.

역자주: 한국어로 번역된 레이아웃 RFC가 있습니다. 😌

// 새로운 App 라우터 ✨
// app/layout.js
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

// app/page.js
export default function Page() {
  return <h1>Hello, Next.js!</h1>;
}

보이는 것보다 더 중요한 건 보이지 않는 것입니다. (app/ 디렉토리를 통해 점진적으로 적용될 수 있는) 새로운 라우터는 완전히 새로운 아키텍처를 가지며, 리액트 서버 컴포넌트서스펜스를 기반으로 구축되었습니다.

// Pages 라우터
// pages/_app.js

// 이 "전역 레이아웃"은 모든 라우트를 감싸게 됩니다.
// 다른 레이아웃 컴포넌트를 구성하는 방법은 없습니다.
// 또한 이 파일로부터 전역 데이터를 가져올 수 없습니다.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

기존 Pages 라우터에서는 레이아웃을 구성할 수 없었고, 컴포넌트와 함께 데이터 페칭을 사용할 수 없었습니다. 하지만 새로운 App 라우터를 사용한다면 이러한 것들이 가능해집니다.

// 새로운 App 라우터 ✨
// app/layout.js
//
// 루트 레이아웃은 전체 애플리케이션에서 공유됩니다.
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

// app/dashboard/layout.js
//
// 레이아웃은 중첩될 수 있고 구성될 수도 있습니다.
export default function DashboardLayout({ children }) {
  return (
    <section>
      <h1>Dashboard</h1>
      {children}
    </section>
  );
}

기존 Pages 라우터에서는 서버로부터 초기 페이로드를 커스터마이징 하기 위해 _document를 사용했습니다.

// Pages 라우터
// pages/_document.js

// 이 파일을 사용하면 서버의 요청에 대한 <html>, <body> 태그를 커스터마이징 할 수 있지만
// HTML 요소를 작성하는 대신 프레임워크 관련 기능을 추가할 수도 있습니다.
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

App 라우터를 사용한다면, 더 이상 Next.js에서 <Html>, <Head>, <Body>를 임포트할 필요가 없습니다. 그저 리액트를 사용하면 됩니다.

// 새로운 App 라우터 ✨
// app/layout.js
//
// 루트 레이아웃은 전체 애플리케이션에서 공유됩니다.
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

새로운 파일시스템 라우터를 구축하는 동안 라우팅 시스템과 관련된 많은 요구사항을 해결할 수 있었습니다. 예를 들면 다음과 같은 요구사항이 있었습니다.

  • 이전에는 _app.js에서만 외부 npm 패키지(컴포넌트 라이브러리와 같은)의 전역 스타일시트만 임포트할 수 있었습니다. 이는 개발자 경험이 좋지 않은 방식이었습니다. App 라우터를 사용하면 이제 어떤 컴포넌트에서든지 CSS 파일을 가져올 수 있습니다. (동일한 위치에도 둘 수 있습니다.)
  • 이전에는 Next.js에서 getgetServerSideProps를 사용하여 서버 사이드 렌더링을 적용하면 전체 페이지가 하이드레이션될 때까지 애플리케이션과의 상호 작용이 차단되었습니다. App 라우터와 함께 리액트 서스펜스를 깊이 통합하여 아키텍처를 리팩터링함으로써 페이지의 일부만 선택적으로 하이드레이트할 수 있게 되었습니다. UI의 다른 컴포넌트가 상호작용하는 것을 차단하지 않게 되는 것입니다. 콘텐츠를 서버에서 즉시 스트리밍하여 체감되는 페이지의 로딩 성능을 개선할 수 있게 되었습니다.

새로운 라우터(App 라우터)는 Next.js가 동작하게 만드는 핵심 요소입니다. 하지만 라우터 자체가 중요하다기보다는, 라우터가 데이터 불러오기와 같은 프레임워크의 나머지 부분을 통합하는 방식이 중요합니다.

자바스크립트로만. 모든 것은 함수

Next.js와 리액트를 사용하는 개발자들은 자바스크립트와 타입스크립트로 코드를 작성하고 애플리케이션 컴포넌트를 함께 구성하고자 했습니다. 기존 글에서는 아래의 코드를 보여드렸습니다.

import React from 'react';
import Head from 'next/head';

export default () => (
  <div>
    <Head>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
    </Head>
    <h1>Hi. I'm mobile-ready!</h1>
  </div>
);

향후 버전의 Next.js에서는 리액트를 자동으로 가져올 수 있도록 DX 개선 사항을 추가했습니다.

이 컴포넌트는 애플리케이션 어디에서든 재사용되고 구성될 수 있는 로직을 캡슐화합니다. 파일시스템 라우팅과 함께 사용하면, 자바스크립트와 HTML을 작성하는 것처럼 리액트 애플리케이션을 쉽게 구축할 수 있습니다.

예를 들어 봅시다. 어떤 데이터를 가져오고 싶다고 할 때 기존에는 아래와 같이 작성했습니다.

import React from 'react';
import 'isomorphic-fetch';

export default class extends React.Component {
  static async getInitialProps() {
    const res = await fetch('https://api.company.com/user/123');
    const data = await res.json();
    return { username: data.profile.username };
  }
}

Next.js 향후 버전에서는 폴리필된 fetch 기능이 추가되어 isomorphic-fetch 또는 node-fetch를 가져올 필요가 없습니다. 클라이언트, 서버 모두에서 웹 fetch API를 사용할 수 있습니다.

프레임워크의 사용이 늘어나며 발달함에 따라, 데이터를 가져오는 새로운 패턴을 모색했습니다.

getInitialProps는 서버와 클라이언트 모두 동작합니다. 이 API는 리액트 컴포넌트를 확장하여 Promise를 생성하고 그 결과를 컴포넌트의 props로 전달합니다.

getInitialProps는 지금도 동작하고 있지만, 사용자의 피드백을 바탕으로 데이터를 가져오는 차세대 API인 getServerSidePropsgetStaticProps를 개발하기 위해 많은 반복 작업을 수행했습니다.

// 라우트의 정적 버전 생성
export async function getStaticProps(context) {
  return { props: {} };
}
// 또는 라우트를 동적으로 서버 렌더링
export async function getServerSideProps(context) {
  return { props: {} };
}

이 API들은 코드가 실행될 때 클라이언트와 서버 모두 명확하게 처리되도록 하며, Next.js 애플리케이션이 자동으로 정적 최적화를 처리합니다. 더불어, 정적 내보내기를 허용함으로써 서버가 지원되지 않는 환경(AWS S3 버킷과 같은)에도 Next.js를 배포할 수 있습니다.

그러나, 이는 "단순한 자바스크립트"가 아니었고, 원래의 설계 원칙을 좀 더 충실해지고 싶었습니다.

Next.js를 개발한 후부터, 우리는 메타의 리액트 코어 팀과 긴밀히 협업하여 리액트의 프리미티브를 기반으로 프레임워크의 기능을 구축했습니다. 리액트 코어 팀의 수년간의 연구와 개발과 양사의 파트너십이 결합하여, 서버 컴포넌트를 포함한 최신 리액트의 아키텍처를 통해 Next.js가 목표를 달성할 기회를 얻었습니다.

App 라우터를 통해, 친숙한 asyncawait 문법으로 데이터를 가져옵니다. 새로 배워야 하는 API가 없는 것이죠. 기본적으로 모든 컴포넌트는 리액트 서버 컴포넌트이기 때문에 데이터를 가져오는 동작은 서버에서 안전하게 이뤄집니다. 예를 들면 아래와 같습니다.

// app/page.js

export default async function Page() {
  const res = await fetch('https://api.example.com/...');
  // 반환 값은 직렬화되지 *않습니다.*
  // 여러분은 Date, Map, Set과 같은 객체를 사용할 수 있습니다.
  const data = res.json();

  return '...';
}

중요한 것은 "데이터 페칭은 개발자의 몫"이라는 원칙이 실현되었다는 점입니다. 데이터를 가져오고 어떤 컴포넌트든지 조합할 수 있습니다. 그리고 단지 퍼스트파티 컴포넌트만 해당하는 건 아닙니다. 서버 컴포넌트와 통합되어 서버에서 완전히 실행하도록 설계된 react-tweet트위터 임베드와 같은 서버 컴포넌트 생태계의 컴포넌트라면 해당합니다.

// app/page.js

import { Tweet } from 'react-tweet';

export default async function Page() {
  return <Tweet id="790942692909916160" />;
}

App 라우터는 리액트 서스펜스와 통합되었으므로, 콘텐츠의 일부가 로딩되는 동안 폴백 콘텐츠를 보다 유동적으로 보여줄 수 있으며 원하는 대로 콘텐츠를 점진적으로 나타낼 수도 있습니다.

// app/page.js

import { Suspense } from 'react';
import { PostFeed, Weather } from './components';

export default function Page() {
  return (
    <section>
      <Suspense fallback={<p>Loading feed...</p>}>
        <PostFeed />
      </Suspense>
      <Suspense fallback={<p>Loading weather...</p>}>
        <Weather />
      </Suspense>
    </section>
  );
}

게다가 App 라우터는 페이지 탐색을 트랜지션을 줌으로써, 라우트 전환을 중단 없이 수행할 수 있습니다.

자동 서버 렌더링 및 코드 분할

Next.js를 개발했을 당시, 개발자들이 웹팩, 바벨 등과 같이 리액트 애플리케이션을 동작하기 위한 도구들을 직접 구성하는 것은 흔한 일이었습니다. 서버 렌더링이나 코드 분할과 같은 추가적인 최적화를 추가하는 것은 커스텀 솔루션에서 구현되지 않는 경우가 많았습니다. 다른 리액트 프레임워크와 마찬가지로 Next.js도 모범 사례를 구현하고 강제하기 위한 추상화 계층을 만들었습니다.

라우트 기반의 코드 분할은 /pages 디펙토리 내의 각 파일이 자체 자바스크립트 번들로 코드 분할하는 것으로 파일 시스템의 크기를 줄이고 초기 페이지 로드 성능을 향상하는 데 도움이 됩니다.

이는 서버 렌더링 되는 애플리케이션뿐만 아니라 Next.js로 만든 싱글 페이지 애플리케이션에도 유용했습니다. 특히 후자의 경우, 처음 로드되는 자바스크립트 번들이 너무 큰 경우가 많았기 때문입니다. 그러나 컴포넌트 수준의 코드 분할을 구현하려면, 개발자들이 next/dynamic을 사용하여 컴포넌트를 동적으로 임포트 해야만 했습니다.

// app/page.tsx

import dynamic from 'next/dynamic';

const DynamicHeader = dynamic(() => import('../components/header'), {
  loading: () => <p>Loading...</p>,
});

export default function Home() {
  return <DynamicHeader />;
}

App 라우터를 사용하면 서버 컴포넌트는 브라우저의 자바스크립트 번들에 포함되지 않습니다. 클라이언트 컴포넌트는 자동으로 코드 분할되는 것이 기본 동작입니다(웹팩 또는 Next.js의 Turbopack 모두 동일). 또한 전체 라우터 아키텍처 상 스트리밍과 서스펜스가 활성화되어 있으므로, UI의 일부를 점진적으로 서버에서 클라이언트로 전송할 수 있습니다.

예를 들어, 조건부 로직에 따라 전체 코드 경로를 분할할 수도 있습니다. 이 예제에서는 로그아웃한 사용자를 위해 대시보드에 대한 클라이언트 사이드 자바스크립트를 로드할 필요가 없는 것이죠.

// app/layout.tsx

import { getUser } from './auth';
import { Dashboard, Landing } from './components';

export default async function Layout() {
  const isLoggedIn = await getUser();
  return isLoggedIn ? <Dashboard /> : <Landing />;
}

Turbopack (베타 단계)

우리의 새로운 번들러인 Turbopack은 Next.js를 통해 테스트 및 안정화 작업 중에 있습니다. Turbopack은 Next.js 애플리케이션에서 작업하고(next dev --turbo 이용) 프로덕션을 빌드하는(next build --turbo) 동안에 로컬 상의 반복 속도를 높여줍니다.

Next.js 13 알파 버전의 릴리즈 이후, 버그를 패치하고 누락된 기능을 추가 지원하기 위해 노력하는 동안 Next.js 13의 도입률이 꾸준히 증가했습니다. 피드백을 수집하고 안정성을 개선하고자 대규모 Next.js 웹사이트를 운영하는 많은 Vercel 고객과 함께 Vercel.com에서 Turbopack을 테스트했습니다. 테스팅과 버그 리포팅을 해주신 커뮤니티의 많은 관심에 감사드립니다.

이제 6개월이 흐른 지금, Turbopack은 베타 단계로 넘어갈 준비가 되었습니다.

Turbopack은 아직 웹팩과 Next.js처럼 완전한 기능상의 동등성을 갖추지는 못했습니다. 이 이슈에서 그러한 기능들에 대한 지원을 추적하고 있습니다. 하지만 대부분의 사용 사례는 이제 지원될 것입니다. 이번 베타 버전의 목표는 Turbopack의 도입률이 증가함과 동시에 남아있는 버그를 지속적으로 해결하고 향후 버전에서 안정성을 갖추는 것입니다.

Turbopack의 증분 엔진(incremental engine)과 캐싱 레이어를 개선하기 위한 투자를 통해 로컬 개발뿐만 아니라 프로덕션 빌드의 속도도 곧 높일 수 있을 것입니다. next build --turbo를 실행하여 즉각적으로 빌드할 수 있는 Next.js의 다음 버전을 기대해 주세요.

Turbopack 베타 버전은 Next.js 13.4에서 next dev --turbo를 실행하여 바로 사용해 보실 수 있습니다.

Server Actions (알파 단계)

리액트 생태계에서는 폼, 폼 상태 관리, 데이터 캐싱과 재검증과 관련된 아이디어에 대한 수많은 혁신과 탐구가 있었습니다. 시간이 지나면서, 리액트는 특정 의견에 무게를 더 두게 되었습니다. 폼 상태에 대해 "제어되지 않는 컴포넌트"를 권장하는 것처럼 말입니다.

현재 솔루션의 생태계는 재사용가능한 클라이언트 사이드 솔루션이거나 프레임워크에 내장된 기본 요소였습니다. 지금까지는 서버 뮤테이션과 데이터 프리미티브를 구성할 방법이 없었습니다. 리액트 팀은 뮤테이션을 위한 퍼스트 파티 솔루션을 개발해 왔습니다.

Next.js에서 실험적인 Server Actions에 대한 지원을 발표하게 되어 기쁩니다. 이는 서버에서 데이터를 변경하고, API 계층을 생성할 필요 없이 바로 함수를 호출할 수 있게 합니다.

// app/post/[id]/page.tsx (Server Component)

import kv from './kv';

export default function Page({ params }) {
  async function increment() {
    'use server';
    await kv.incr(`post:id:${params.id}`);
  }

  return (
    <form action={increment}>
      <button type="submit">Like</button>
    </form>
  );
}

Server Actions을 사용하면, 강력한 서버 우선 데이터 변경과 함께 더 적은 클라이언트 사이드 자바스크립트 그리고 점진적으로 강화된 폼을 얻게 됩니다.

// app/dashboard/posts/page.tsx (Server Component)

import db from './db';
import { redirect } from 'next/navigation';

async function create(formData: FormData) {
  'use server';
  const post = await db.post.insert({
    title: formData.get('title'),
    content: formData.get('content'),
  });
  redirect(`/blog/${post.slug}`);
}

export default function Page() {
  return (
    <form action={create}>
      <input type="text" name="title" />
      <textarea name="content" />
      <button type="submit">Submit</button>
    </form>
  );
}

Next.js에서 Server Actions은 Next.js 캐시, 증분 정적 재생성(Incremental Static Regeneration, ISR), 그리고 클라이언트 라우터를 포함하여 데이터의 생명주기를 깊게 결합하도록 설계되었습니다.

새로운 API인 revalidatePathrevalidateTag를 통해 데이터를 재검증한다는 것은 데이터의 변경과 페이지의 리렌더링, 또는 리다이렉트가 하나의 네트워크 왕복에서 일어날 수 있음을 의미합니다. 심지어 업스트림의 공급자의 속도가 느릴지라도 올바른 데이터가 클라이언트에 표시됨을 보장합니다.

// app/dashboard/posts/page.tsx (Server Component)

import db from './db';
import { revalidateTag } from 'next/cache';

async function update(formData: FormData) {
  'use server';
  await db.post.update({
    title: formData.get('title'),
  });
  revalidateTag('posts');
}

export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['posts'] } });
  const data = await res.json();
  // ...
}

Server Actions은 필요에 따라 설정할 수 있도록 설계되었습니다. 리액트 커뮤니티 구성원이라면 Server Actions을 구축하고 게시할 수 있으며 생태계에 배포할 수 있습니다. 서버 컴포넌트와 마찬가지로, 우리는 클라이언트와 서버 모두를 위한 구성 가능한 프리미티브의 새로운 시대가 열리기를 기대하고 있습니다.

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: true,
  },
};

module.exports = nextConfig;

다른 개선사항

  • 드래프트 모드: 드래프트 콘텐츠를 헤드리스 CMS에서 가져오고 렌더링할 수 있습니다. 드래프트 모드는 pagesapp 모두에서 동작합니다. 기존 Preview Mode API를 개선하고 단순화하여 pages에서도 동작합니다.

자주 묻는 질문

App 라우터가 안정화 단계에 들어섰다는 것은 무엇을 의미하나요?

App 라우터가 안정화 단계에 도달했다고 해서 작업이 종료되었다는 것은 아닙니다. App 라우터의 핵심 기능이 프로덕션 환경에서 사용될 수 있을 만큼 준비가 되었으며 자체 내부 테스트와 App 라우터를 미리 사용해 본 사용자들의 검증을 거쳤음을 의미합니다.

Server Actions의 완전한 안정화를 포함하여 추가로 최적화할 기능들이 여전히 남아 있습니다. Next.js 커뮤니티가 지금 애플리케이션을 배우고 구축하는 데 있어 어디서부터 시작해야 할지 명확하게 알 수 있게 안정성을 확보하는 것이 중요했습니다.

App 라우터는 서버 컴포넌트와 같은 기능을 프레임워크에 적용할 수 있는 리액트의 canary 채널 위에 구축되었습니다. 여기서 자세히 알아보세요.

Next.js 베타 문서는 어떻게 되나요?

이제 App 라우터를 사용하여 새 애플리케이션을 구축하는 것을 권장드립니다. Next.js 베타 문서는 App 라우터를 설명하기 위해 처음부터 다시 작성되었는데요. 이제 안정화 Next.js 문서에 다시 합쳐집니다. 이제 App 라우터와 Pages 라우터 사이를 쉽게 전환할 수 있습니다.

App 라우터를 도입하는 법을 알고 싶다면 App 라우터 점진적 도입 가이드를 읽어보시길 바랍니다.

기존 Pages 라우터는 아예 없어지는 건가요?

아닙니다. 앞으로의 여러 주요 버전에 대해 버그 수정, 개선, 보안 사항 수정 등을 포함하여 pages/ 개발을 지원하는 데 최선을 다하고 있습니다. 개발자가 점진적으로 App 라우터를 도입할 수 있도록 충분한 시간을 확보하고자 합니다.

프로덕션 환경에서 pages/app/를 동시에 사용하는 것을 지원하며 또한 권장하고 있습니다. App 라우터는 각 라우트 별로 적용할 수 있습니다.

서버 컴포넌트가 "완성"되었다는 의미인가요?

Next.js는 서버 컴포넌트를 포함한 리액트 아키텍처를 기반으로 구축되는 하나의 프레임워크입니다. App 라우터에서 제공하는 경험이 다른 프레임워크(또는 새로운 프레임워크)에서도 이러한 아키텍처를 도입하게 되기를 바랍니다.

이 생태계에는 무한 스크롤과 같이 아직 정의되지 않은 패턴들이 존재합니다. 현재로서는 생태계가 성장하고 관련된 라이브러리가 만들어지거나 업데이트되기까지는 클라이언트 솔루션을 사용하는 것이 좋습니다.

커뮤니티

Next.js는 2,600명 이상의 개발자, 구글과 메타와 같은 산업 파트너, Vercel의 코어 팀이 함께 노력하여 일궈낸 결과물입니다. 깃허브 디스커션, 레딧, 디스코드를 통해 커뮤니티에 참여해 보세요.

이번 릴리즈는 아래의 사람들과 함께 만들었습니다.

컨트리뷰션에 참여해 주신 분들은 다음과 같습니다. @shuding, @huozhi, @wyattfry, @styfle, @sreetamdas, @afonsojramos, @timneutkens, @alexkirsz, @chriswdmr, @jankaifer, @pn-code, @kdy1, @sokra, @kwonoj, @martin-wahlberg, @Kikobeats, @JTaylor0196, @sebmarkbage, @ijjk, @gnoff, @jridgewell, @sagarpreet-xflowpay, @balazsorban44, @cprussin, @ForsakenHarmony, @li-jia-nan, @dciug, @albertothedev, @DuCanhGH, @feedthejim, @patrick91, @padmaia, @sophiebits, @eps1lon, @reconbot, @acdlite, @cjmling, @nabsul, @motopods, @hanneslund, @tunamagur0, @devknoll, @apeltop, @maranomynet, @y-tsubuku, @EndangeredMassa, @ykzts, @AviAvinav, @adilansari, @wyattjoh, @charkour, @delbaoliveira, @agadzik, @Just-Moh-it, @rodrigofeijao, @leerob, @juliusmarminge, @koba04, @Phiction, @jessewarren-aa, @ryo-manba, @Yovach, @dylanjha.

2개의 댓글

comment-user-thumbnail
2023년 6월 13일

좋은글 감사합니다!

답글 달기
comment-user-thumbnail
2023년 7월 2일

감사감사감사감사합니다

답글 달기