기술 이야기
home
Programming
home
🔍

넥슨 크리에이터즈 플랫폼의 전세계 도약을 위한 검색 엔진 최적화 여정

Upload date
2024/04/15
Tag
UX
자동화
프로트엔드
개발자
Editor
넥슨크리에이터즈팀_최종근, 기술연구팀_조양진
Editor is
최종근 최고의 DX와 UX로 완성도 높은 프로덕트를 지향합니다. 조양진 좋아하는 것에 진심인, 꿈나무 개발자 조양진입니다.
상태
업로드 대기
2 more properties

Table of contents

들어가며

넥-하! 넥슨크리에이터즈팀 최종근, 엔진스튜디오 기술연구팀 조양진입니다. 저희는 전 세계 유저에게 게임과 관련된 개인화 콘텐츠를 제공합니다. 더 나아가 콘텐츠를 제작하는 크리에이터즈들과 서포터가 상생할 수 있는 플랫폼, “Creators Platform”을 준비하고 운영하고 있습니다.
Creators Platform은 크리에이터즈가 적극적으로 넥슨 게임 콘텐츠를 제작할 수 있도록 지원하고, 서포터가 크리에이터즈를 후원하여 쿠폰 등의 보상을 받을 수 있도록 하는 win-win 플랫폼입니다.
그림 1: Creators Platform 후원 크리에이터즈 등록 화면
서포터의 경우 쿠폰을 수령하기 위해 필연적으로 응원할 크리에이터즈 한 명을 선택하고 후원을 등록하는 과정이 필요합니다. 해당 과정에서 크리에이터즈에게 발급된 고유의 링크를 통해 진행할 수 있는데, 이 때 외부 서비스를 통해 URL이 공유되는 일이 잦을 것이라 예상하였습니다.
그림2: 오픈 그래프가 적용되지 않아 미리보기가 없는 URL
그러나 위와 같이 미리보기 데이터가 준비되지 않아 어떤 크리에이터즈의 URL인지 확인할 수 없는 불편함이 존재했습니다. 이를 수정하기 위해 오픈 그래프라는 프로토콜을 사용하기로 하였습니다.

1. 오픈 그래프(Open Graph), 넌 누구니?

오픈 그래프 프로토콜은 Facebook에서 시작된 웹 페이지의 메타 정보를 표현할 수 있는 프로토콜입니다. 이 프로토콜을 사용하면 SNS나 메신저에서 링크 미리보기를 볼 수 있고, 검색 봇이 해당 내용을 우선적으로 크롤링하게 되어 검색 결과에 노출된 링크의 내용을 유저가 추측하는 데 도움을 줍니다. 뿐만 아니라 검색 엔진 최적화(SEO) 측면에서도 긍정적으로 반영되어 검색 엔진에 노출될 확률도 높아지고 웹 페이지가 높은 가치를 갖는다고 간주됩니다. 오픈 그래프 프로토콜을 준수하기 위해서는 HTML <meta> 태그를 사용합니다.
<meta> 태그란? <meta>  태그는 검색 엔진과 기타 클라이언트에 페이지에 대한 추가 정보를 제공하는 데 사용되는 HTML 태그입니다.
현재 Creators Platform 프론트엔드 기술 스택으로 Bundler는 Vite, UI Framework는 React를 사용합니다. 프로젝트를 빌드하면 정적인 index.html 템플릿 하나가 생기며 URL을 통한 클라이언트 사이드 라우팅을 기반으로 앱의 화면 상태를 제어하고 있습니다.
따라서 index.html 파일 하나에 의존하는 방식에서 일반적인 방법으로는 다이나믹한 <meta> 태그를 가질 수 있는 방법이 없다고 판단하였으며, 각 페이지(URL)별 오픈 그래프 <meta> 태그를 지원하기 위해 여러 가지 방법을 시도해 보았습니다.

2. 시도한 방법

2-1. <meta> 태그를 동적으로 변경

처음으로 JavaScript를 사용하여 동적으로 <meta> 태그 내용을 수정하는 시도를 해보았습니다.
// 클라이언트 라우팅을 관리하는 컴포넌트 useEffect(() => { if (!isProfilePath) return; const title = `${creatorsInfo.nickname} (${creatorsInfo.creatorCode})님의 프로필 | 넥슨 크리에이터즈`; document.title = title; // javascript으로 title을 동적으로 변경합니다. // ... document.querySelector('meta[property="og:description"]') .setAttribute('content', title) // ... }, [isProfilePath, creatorsInfo]);
TypeScript
복사
해당 방법이 잘 작동하는 것처럼 보였으나 이내 의미 없다는 사실을 깨달았습니다. 일부 크롤러나 봇은 페이지의 JS 로직이 아닌 단순히 URL을 통해 반환된 템플릿 HTML을 읽기 때문에 실제로 마운트되는 DOM을 알 수 없기 때문입니다. GoogleBot과 같이 JS를 실행할 수 있는 봇의 경우에는 정상적으로 파싱할 수 있지만, 저희 서비스와 같은 복잡한 웹 애플리케이션 구조에서는 의도치 않은 영역을 파싱하여 원치 않는 미리보기 콘텐츠가 노출될 수 있습니다.
위 사진을 보시면 실제 웹 페이지의 내용이 정상적으로 크롤링되긴 했지만, 렌더링된 텍스트 전체를 기계적으로 읽어와 정제되지 않은 정보들이 단순하게 나열되어 있습니다. 따라서 동적으로 <meta>를 수정하는 것 외의 다른 방법을 모색해야만 했습니다.

2-2. 별도 서버 구축(SSR)

론칭 당시 Creators Platform은 크리에이터즈만 적립된 포인트나 서포터 현황을 살펴볼 수 있는 간단한 사이트였습니다. 뿐만 아니라 사이트 내용의 대부분은 개인화된 콘텐츠이기 때문에 서버 사이드 렌더링 지원은 공수에 비해 얻는 실질적으로 얻는 이득이 크지 않을 것이라 판단이 되어 클라이언트 사이드 렌더링을 유지하고 있습니다.
react-snap, react-helmet 등의 라이브러리들을 찾아봤지만, 완벽한 솔루션은 아니었습니다. react-snap은 생성될 모든 페이지를 전부 탐색하여 미리 렌더링 해 놓는 방식인데, 이는 동적으로 변하는(ex. 매번 추가되는 크리에이터즈의 정보, 지속적으로 업데이트 되는 공지사항 글 정보) 페이지에 대응하여 오픈 그래프를 적용하기 어려울 것이라고 판단하였습니다. react-helmet은 SEO를 지원한다기보다는, JSX 문법으로 쉽게 <head> 에 있는 요소들을 다루는 것에 가까웠습니다.
따라서 동적인 콘텐츠에 대해 오픈 그래프를 적용하려면 서버 사이드에 연산이 필수적으로 필요하다는 결론에 이렀습니다. 지금 있는 프로젝트의 구조를 바꾸기보단, 실제 웹 애플리케이션과의 관심사를 분리하면서 오픈 그래프 생성을 도와줄 수 있는 방법을 생각해 보았습니다.
냐하! 우당탕탕 NIA 개인화 서비스 개발기에서는 EC2 서버를 거쳐 오픈 그래프를 가져올 수 있도록 설계한 적이 있습니다. 처음에는 이처럼 별도의 서버를 통해 관리하자는 생각을 했습니다.
실제로도 잘 동작하고, 괜찮은 방법이었습니다. 하지만 개발 일정 및 인프라 복잡도가 증가하기 때문에 이 방법을 당장 적용하기는 어려웠습니다. 또한 여러 프로젝트를 진행하면서 새 프로젝트가 생성될 때 마다 서버 형상을 생성, 관리하는 비용이 늘어나는 것에 대한 부담이 있었습니다.
그래서, ‘서버리스’ 방식을 활용하면 부담 없이 기존 서비스에 새로운 워크플로우를 구축할 수 있을 것이라고 생각하였습니다. 관리 포인트가 줄면서도 간결한 로직만으로 원하는 바를 달성할 수 있을 것이라 기대했습니다.

2-3. 서버리스 (with AWS Lambda)

크리에이터즈 플랫폼은 AWS 인프라 위에서 서비스되고 있습니다. 프론트엔드는 정적 파일로 빌드되며 빌드된 파일은 S3에 적재되고 CloudFront로 빌드를 캐싱하여 전 세계에 낮은 레이턴시로 서비스를 제공하고 있습니다.
그림4: 크리에이터즈(후원자)가 서비스에 접속하면 CloudFront로부터 S3가 원본인 캐싱된 빌드를 받게 된다.
CloudFront는 pathname에 따라 다양한 서비스 (EC2, Lambda, API Gateway 등)을 연결할 수 있습니다. 이를 ‘원본’이라고 하는데 CloudFront가 어떤 원본을 가져올 지는 Pathname에 따라 결정합니다. pathname은 wildcard를 지원합니다. 따라서 다음과 같은 시나리오를 생각할 수 있습니다.
/static /oauth /assets/ 및 확장자가 있는 파일은 확실히 Lambda를 거칠 필요가 없이 정적 파일만 서빙해도 충분합니다. 다만, 웹 브라우저에 표시되고 공유될 가능성이 있는 URL (/creators/list /creators/1 등)은 Lambda를 거쳐서 추가적인 메타 데이터를 가져올 수 있도록 하였습니다.
정리하자면 다음과 같이 4단계의 프로세스로 정리할 수 있습니다.
1.
Lambda 함수에 로직을 작성합니다.
2.
Lambda 함수의 실행을 통해 HTML 파일을 가져옵니다.
3.
URL을 기반으로 Creators Platform의 API를 호출합니다.
4.
해당 정보를 메타태그에 추가하여 클라이언트에 반환합니다.
따라서 다음 그림과 같은 플로우로 동작하게 됩니다.
그림6: Lambda에서 API 통신을 통해 필요한 서버 정보를 취득하는 플로우
Lambda 함수에 다음과 같이 작성하였습니다.
// AWS Lambda의 경우 런타임으로 Nodejs 18.x 를 사용할 수 있으며 ESM을 기본적으로 지원합니다. export const handler = async (event) => { const host = event.headers.host; // 유입한 호스트가 무엇인지를 판단 ex: creators-support.nexon.com const { rawPath, rawQueryString } = event; // pathname과 querystring을 추출 const rawHTML = await getS3Object(host, rawPath, rawQueryString, /* ... */); // 기본적인 index.html 템플릿 파일을 얻어옵니다. const HTMLMetaAttached = await getHTMLMetaAttached(rawHTML, frontUrl); // API 서버와 통신하여 템플릿에 opengraph 메타 태그를 추가합니다. return { statusCode: 200, headers: { "content-type": "text/html; charset=UTF-8", }, body: HTMLMetaAttached, };
JavaScript
복사

3. DX 개선

작동은 했지만 구체적으로 파이프라인을 구축하면서 자잘하게 생겼던 버그 뿐만 아니라 실제 운영 환경상에서의 이슈가 다수 발생하였습니다. 그럴 때마다 AWS 콘솔에서 직접 코드를 수정, 배포, 테스트하는 과정이 반복적이라 피로감을 느꼈습니다.
다음과 같이 3가지 방법을 통해 개선하였습니다.
테스트
AWS 자체에 테스트 기능이 존재하지만 매번 콘솔에 접속해서 테스트하는 것은 무리가 있다고 판단했습니다.
개발 환경과 운영 환경의 컨텍스트 스위칭 비용을 줄이기 위해서 먼저 테스트 코드를 작성하여 의도한 대로 메타 태그가 잘 생성 되었는지 체크할 수 있게 되었습니다.
CI/CD
빌드 및 배포 자동화를 통해 무의미하고 반복 작업을 줄여 DX를 개선하였습니다.
Lambda 자동 업로드
작성한 로직과 해당 로직에 사용된 라이브러리를 운영환경에서 손쉽게 사용할 수 있도록 개선하였습니다.

3-1. 로컬 테스트

테스트의 필요성

실제로 AWS에 업로드하고 적절한 위치에 오픈 그래프 <meta> 태그가 추가되었는지 확인하는 작업이 반복적이라고 느꼈습니다. 게다가 페이지나 유저별로 수 많은 시나리오가 존재하는데, 이를 일일이 확인하는 것도 번거로웠습니다. 따라서 테스트 도구를 도입하여 비즈니스 로직이나 테스트 케이스를 수정할 때마다 곧바로 확인할 수 있다면 개발 편의성과 생산성이 늘어날 것이라는 기대를 했습니다. 예를 들어 모든 테스트 케이스가 통과되어야 commit이 가능해지는 pre-commit hook을 추가하여 매 커밋마다 안정성을 보장 받을 수 있습니다.

테스트 도구 고르기

테스트는 빠르고 쉽게 도입할 수 있어야 하며, 최신 문법을 지원하고 그 과정이 어렵지 않은 것을 고르고 싶었습니다. vitest가 그 니즈를 만족 시켜주는 툴 중 하나입니다. Jest만큼 익숙하고 속도도 빠르며 소스 코드 변화에 민감하고 ESM을 완벽하게 지원합니다. vite와 잘 동작하지만 꼭 vite를 써야하는 것도 아닙니다.

테스트 코드 작성

테스트 코드 작성은 비교적 수월하게 이루어졌는데요, AWS가 실행하는 작업을 모의하면 됩니다. 지정된 handler함수의 event 인자에 적절한 값들을 집어넣고 response를 return 하는 구조라 입력과 출력이 명확하므로 테스트하기 적절하고 mock data를 쉽게 넣을 수 있었습니다.
입력
client에서 요청한 URL: https://creators-support.nexon.com/kr/path-to-support?queryA=B&C=D
출력
해당하는 URL에 대응하는 META Tag 등이 포함된 html string
인풋과 아웃풋이 명확하다면 테스트 케이스는 쉽게 생성할 수 있습니다.
const open = async (frontUrl: string) => { const obj = createTestObject(frontUrl); return await awsHandler(obj); }; const getMetaTag:(node: Node, metaKey: string) => string | null = (node, metaKey) => { /* 해당 메타태그에서 value를 리턴 */ } test("Creators Platform 페이지 접속 점검", async () => { const result = await open("https://creators-support.nexon.com"); expect(result.statusCode).toBe(200); }); describe("정의된 Case 외 공통", async () => { it.concurrent("랜딩 페이지", async ({ expect }) => { const { body } = await open("https://creators-support.nexon.com/kr/landing"); const htmlNode = parse(body); expect(getMetaTag(htmlNode, "title"), "Nexon Creators"); expect(getMetaTag(htmlNode, "description"), "Have fun more with Nexon Creators"); expect(getMetaTag(htmlNode, "og:description"), "Have fun more with Nexon Creators"); expect(getMetaTag(htmlNode, "image"), "https://.../creators-platform/nexon_creators_og.png"); }); it.concurrent("크리에이터즈 둘러보기", async ({ expect }) => {/* ... */}); it.concurrent("크리에이터즈 후원", async ({ expect }) => {/* ... */}); });
TypeScript
복사

환경 변수 주입

로컬에서 테스트하려면 Creators Platform 빌드에서 템플릿을 얻어오기 위해 S3에 접근할 수 있는 credentials이 필요하여 다음과 같이 설정하였습니다.
const s3Client = new S3Client( process.env.NODE_ENV === "test" ? { region: "...", credentials: { accessKeyId: process.env.ACCESS_KEY_ID as string, secretAccessKey: process.env.SECRET_ACCESS_KEY as string, }, } : {} );
TypeScript
복사
이를 위해 dotenv를 활용하였습니다. 사실상 proecess.env.NODE_ENVtest일 때만 환경 변수를 읽어올 필요가 있어서 다음과 같이 vitest 설정도 변경하였습니다.
import { defineConfig } from "vitest/config"; export default defineConfig({ test: { // ... setupFiles: ["dotenv/config"], // this line, }, });
TypeScript
복사
webpack 에서는 프로덕션 번들링할때만 필요하므로 별도의 환경 변수가 필요하지는 않습니다 (프로덕션 코드가 실행되는 환경에서는 credentials 없이 동작합니다).
그림7: 테스트는 사진과 같이 자동으로 진행되며, 소스코드를 수정하거나 커밋하기 전에 실시간으로 수행된다.

3-2. CI/CD

CI/CD를 통해 이루고자 하는 목표는 크게 두 가지입니다.
1.
작성된 코드를 빌드(Build)하는 과정을 자동화
2.
빌드 결과를 Lambda에 자동 배포(Deploy)하는 것
저희는 버전 관리 시스템으로 git을 사용하고 gitlab 플랫폼을 사용하기 때문에, 저장소 최상단에 .gitlab-ci.yml 을 작성해야 합니다.
우선 사용할 Docker 이미지와 단계를 정의해줍니다.
stages 키워드를 사용하여 우리가 사용할 단계를 정의할 수 있습니다.
저희는 위에서 크게 두 가지 단계로 나누었으니 build와 deploy를 사용하겠습니다.
image: node:18.12.1 cache: paths: - ./node_modules/ stages: - build - deploy
YAML
복사
우선 build stage에서 실행할 스크립트를 작성합니다.
build-lambda: stage: build script: - npm i - npm run build
YAML
복사
npm i를 통해 필요한 모듈을 설치하고, package.json에 정의 되어있는 스크립트 build를 실행합니다. (webpack)
이후 artifacts를 정의하여 빌드 아웃풋을 보관합니다.
artifacts: paths: - ./index.mjs when: on_success expire_in: "3 mos"
YAML
복사
성공 시 index.mjs 파일을 3달 동안 보관하게 될 것입니다.
이제 stage 단계를 정의할 차례입니다.
deploy-lambda: stage: deploy needs: [build-lambda] dependencies: - build-lambda
YAML
복사
이전 단계가 완료된 이후에 실행 되도록 needs를 [build-lambda]로 설정하고 아까 설정한 artifacts를 사용할 수 있도록 dependencies에 build-lambda를 정의합니다.
image: name: amazon/aws-cli:latest entrypoint: [""] script: - yum install -y zip - mkdir dist - zip -r dist/opengraph.zip . -i index.mjs - aws lambda update-function-code --function-name <람다 이름> --zip-file fileb://dist/opengraph.zip
YAML
복사
이번에는 aws-cli docker 이미지를 사용하여 배포합니다.
참고로 amazon/aws-cli의 경우 기본 entrypoint가 aws로 설정되어 있으니, 바로 aws-cli 커맨드를 사용하는 것이 아니기 때문에 entrypoint:[""]을 지정하여 entrypoint를 오버라이딩 해줍니다. 또한 aws-cli는 redhat 계열의 이미지이므로 (2.13.30 기준)
yum package manager를 사용하여 zip 패키지를 설치하는 스크립트를 작성하였습니다.
이후 빌드 결과물로 나온 index.mjs를 opengraph.zip으로 압축하고, aws lambda update-function-code 명령어를 사용하여 Lambda에 적용하였습니다.
하지만 해당 스크립트를 실행 할 때마다 매번 zip 패키지를 설치해야 한다는 문제점을 발견했습니다. 따라서 이를 해결하기 위해 저희는 커스텀 도커이미지를 사용하기로 하였습니다. 공용으로 사용하는 GitLab runner의 EC2 인스턴스 내부에 로컬 이미지를 만들고, 이미지를 GitLab container registry에 등록하여 불필요한 docker pull을 줄이는 사용하는 방식을 채택했습니다.
따라서 해당 EC2에 ssh로 접근하여 다음과 같은 dockerfile을 작성하였습니다.
FROM amazon/aws-cli RUN yum install -y zip
Docker
복사
이후 docker build -t aws-cli-with-zip 을 입력하여 레지스트리에 등록하였습니다.
docker save -o aws-cli-with-zip.tar aws-cli-with-zip docker load -i aws-cli-with-zip.tar docker tag aws-cli-with-zip <private-registry-domain>/<repository>/aws-cli-with-zip:2.13.31 //태그를 지정하여 registry에 등록가능. aws-cli의 최신 버전이 2.13.31이었기에, 해당 버전을 달았습니다. docker image push <private-registry-domain>/<repository>/aws-cli-with-zip:2.13.31
Bash
복사
GitLab의 경우 등록된 이미지를 확인할 수 있는 web ui도 제공하고 있는데요.
‘Packages & Registries’ 메뉴에서 잘 등록되었는지 확인 및 삭제가 가능합니다.
이제 aws-cli-with-zip 이미지는 aws-cli 이미지에 zip 패키지가 이미 설치된 상태로 제공되니 위에서 설명드린 yum install -y zip 을 제거할 수 있었습니다.
상기한 자동화 과정을 통해 매번 수작업으로 오픈 그래프 코드를 수정하는 일 없이 작업할 수 있게 되었습니다. 참고로 오픈 그래프 관련 폴더에 수정이 있을 때만 CI가 작동할 수 있도록 gitlab-ci.yml 파일의 only:changes 키워드를 사용하면 더욱 깔끔하게 CI 액션을 관리할 수 있습니다.

3-3. Lambda 자동 업로드를 위한 번들링

오픈 그래프 로직 개발 초기에는 단일 JS 파일에 Lambda 로직을 작성하는 것 만으로도 충분했지만 HTML을 편하게 파싱할 수 있는 라이브러리를 사용하는 등, 외부의 npm 생태계를 활용하고 싶었습니다. Lambda의 경우 격리된 환경이므로 외부의 라이브러리를 다운로드 받아 사용하기 어렵기 때문에, 로컬에서 사용하는 node_modules 폴더를 같이 Lambda에 업로드해야 합니다. 그러나 파일 수가 너무 많으면 함수가 제대로 동작하지 않으므로 파일을 단일화하고 압축할 필요가 있었습니다.
또한 로직이 길어지며 단일 파일의 코드를 작성하면 유지 보수를 진행하기 어렵다는 판단 아래, 여러 파일로 분리해서 작성하였으며, TypeScript를 사용하였기 때문에 더더욱 트랜스파일링 과정이 필요했습니다.
저희가 찾은 정답은 webpack을 사용하여 소스코드를 번들링 트랜스 파일링을 진행 할 수 있도록 구성하는 것 이었습니다. 그 과정에서 문제점이 있었는데, 바로 webpack 빌드를 실행하면 결과물이 0바이트로 출력되는 것이었습니다.
원인을 파악해 보니 AWS Lambda에서 요구하는 코드 구조는 export 된 handler 이름으로 된 함수가 있어야 하는데, 이는 AWS로 이벤트가 수신되면 (ex. 크리에이터즈 플랫폼으로 접속하면) handler 함수를 호출합니다.
따라서, 일반적인 node 애플리케이션과 다르게 코드베이스 상에서 직접적으로 해당 handler 를 호출하는 곳이 없기 때문에 해당 코드가 Tree Shaking 되어 지워졌던 것입니다.
결과적으로 Tree Shaking을 방지하기 위해 handler 함수를 보존해야 하기 때문에 다음과 같이 웹팩 설정을 하였습니다.
const path = require("path"); module.exports = { entry: "./src/index.ts", mode: "production", target: "node18.12", module: { rules: [ { test: /\.ts$/, use: "ts-loader", exclude: /node_modules/, }, ], }, resolve: { extensions: [".ts", ".js"], }, output: { library: { type: "module", }, filename: "index.mjs", path: path.resolve(__dirname), }, experiments: { outputModule: true, }, };
JavaScript
복사
여기서 주목해야 할 부분은 output.library.type과 experiments.outputModule 입니다.
output.library는 엔트리포인트에서 export로 내보내는 라이브러리로 간주할 수 있으며 최종 빌드에 인터페이스와 구현이 유지됩니다. ESM 방식으로 내보내야 하므로 "module"을 선택했습니다. (이 기능은 실험적이어서 experiments.outputModule 도 같이 지정하였습니다.)
이 설정으로 webpack 빌드를 수행하면 다음과 같이 단일 파일로 성공적으로 빌드된 결과물이 생성되었습니다.
결과물을 확인해보니 s3에 접근하기 위한 @aws-sdk/client-s3 라이브러리가 소스코드가 포함되어 index.mjs 파일의 크기가 매우 커지는 문제가 발생했습니다.
그나마 희망적인 소식은 AWS Lambda 런타임 환경에서는 node_modules 로 해당 패키지를 포함하지 않아도 AWS 관련 모듈을 사용할 수 있습니다. 따라서, 해당 모듈을 호출하지만 번들링에서는 제외하는 방법이 필요했습니다. 다음과 같이 webpack을 구성한다면, aws-sdk는 더 이상 번들링에 포함되지 않습니다.
const path = require("path"); module.exports = { // ... externals: { "@aws-sdk/client-s3": "@aws-sdk/client-s3", }, // ... };
JavaScript
복사
이후 다시 빌드된 코드를 확인하여 해당 aws-sdk 라이브러리 코드가 차지하던 용량이 절약된 것을 확인할 수 있습니다.

나가며

이상으로 모든 여정을 마치겠습니다. 이제 유저가 URL을 공유하면 알맞은 <meta> 태그가 마운트되어 연결된 웹 페이지의 내용이 정확히 크롤링되고 올바른 의미를 전달할 수 있게 되었습니다.
그림15: Creators Platform의 한국 서비스 랜딩
이로써 Creators Platform은 크리에이터즈, 서포터 모두에게 각인될 수 있는 서비스가 될 것입니다. 긴 글 읽어 주셔서 감사합니다.
같이 즐겁게 개발하실 분들을 모집하고 있습니다. 아래 링크를 확인해주세요!
넥슨 인텔리전스랩스 채용 공고 확인하기
엔진스튜디오 채용 공고 확인하기
채용 공고는 일정 기간 이후 마감될 수 있습니다.

Reference

함께 읽으면 좋은 글
Techblog Contents
Related Sites
 넥슨 게임 포탈
회사 소개
인텔리전스랩스 소개
인재 영입
인텔리전스랩스 블로그 운영 정책
 테크블로그 문의 devrel@nexon.co.kr