전날 채널에 userId를 붙였다. 배포하니까 이제 진짜 문제들이 터지기 시작했다.
@shorts/shared에서 prisma, s3, logger 같은 서브모듈을 나눠서 export하고 있었는데, package.json의 exports 필드에 루트 하나만 정의돼 있었다.
// packages/shared/package.json
"exports": {
".": "./dist/index.js",
"./prisma.js": "./dist/prisma.js",
"./s3.js": "./dist/s3.js",
"./logger.js": "./dist/logger.js",
"./env.js": "./dist/env.js",
"./types.js": "./dist/types.js"
}
web에서 @shorts/shared/prisma.js를 import하는 순간 webpack이 resolve를 못 했다. 서브패스마다 exports map에 명시해야 하는 걸 몰랐다.
exports는 고쳤는데 이번엔 Docker 이미지에서 dist/prisma.js가 아예 없다고 났다. Dockerfile에서 shared 패키지를 build만 하고 prisma generate를 안 돌리고 있었다.
COPY . .
RUN pnpm --filter @shorts/shared prisma:generate && pnpm --filter @shorts/shared build
Prisma 엔진 바이너리가 OpenSSL 1.1 계열로 빌드돼 있었는데, node:20-slim이 Debian Bookworm 기반이라 OpenSSL 3만 들어있다. bullseye-slim으로 내렸다.
-FROM node:20-slim AS base
+FROM node:20-bullseye-slim AS base
auth 페이지들도 빌드 타임 프리렌더에서 AUTH_SECRET 없이 auth()를 호출해 에러가 났다. force-dynamic으로 프리렌더 자체를 껐다.
export const dynamic = 'force-dynamic';
Sidebar에서 useSession()으로 session.user.id를 읽게 바꿨는데, providers.tsx에 SessionProvider를 안 감싸놨다. TypeError.
return (
<SessionProvider>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</SessionProvider>
);
jwt 콜백이 신규 로그인(user 파라미터가 있을 때)에만 userId를 채우고 있었다. userId 붙이기 전에 이미 로그인해 있던 세션은 영영 채워지지 않는다는 뜻이다. 토큰에 email로 재조회하는 경로를 추가했다.
async jwt({ token, user }) {
const email = (user?.email ?? token.email as string | undefined)?.toLowerCase();
if (email && !token.userId) {
const found = await prisma.user.findUnique({ where: { email }, select: { id: true } });
if (found) token.userId = found.id;
}
...
}
bullseye로 내렸지만 binaryTargets에 debian-openssl-1.1.x가 없어서 standalone 빌드에 엔진 바이너리가 안 실렸다. 세 군데를 같이 고쳐야 했다.
binaryTargets = ["native", "debian-openssl-1.1.x", "rhel-openssl-3.0.x"]
next.config.ts에 serverExternalPackages와 outputFileTracingRoot를 추가하고, Dockerfile에서 엔진 파일을 찾아 runner 스테이지로 명시 복사했다.
RUN find /app -name "libquery_engine-debian-openssl-1.1.x.so.node" | head -1 | xargs -I{} cp {} /tmp/prisma-engine-debian.so.node
...
COPY --from=builder /tmp/prisma-engine-debian.so.node ./apps/web/.prisma/client/libquery_engine-debian-openssl-1.1.x.so.node
로그인 계정을 자동 선택시키려고 login_hint 파라미터를 넣었다가, authuser와 얽혀 redirect_uri 검증이 깨지는 걸 확인하고 바로 되돌렸다. 대신 콜백 자체를 우리 도메인(shortsautomation.com)을 경유하는 프록시로 만들었다.
export async function GET(request: NextRequest) {
const apiBase = process.env.NEXT_PUBLIC_API_URL ?? '';
return NextResponse.redirect(`${apiBase}/auth/youtube/callback${request.nextUrl.search}`, { status: 302 });
}
콜백에서 code가 없이 들어오는 케이스도 있어서 optional로 바꾸고 try-catch로 감쌌다. 실패하면 에러 메시지를 붙여서 /close로 리다이렉트한다.
try {
const channel = await this.authService.handleCallback(code, state);
res.code(302).redirect(`${webOrigin}/close?channelId=${channel.id}`);
} catch (err) {
const msg = err instanceof Error ? err.message : 'OAuth 처리 실패';
res.code(302).redirect(`${webOrigin}/close?auth_error=${encodeURIComponent(msg)}`);
}
하루 만에 인증 관련 버그를 여섯 개 넘게 잡았다. 배포 환경은 로컬이랑 다르다는 걸 다시 확인한 날.