[NestJS] ejs 템플릿엔진 nodemailer로 발송시 CSS 미적용 오류 해결
NestJS에서 SSR을 해야할 일이 생겼다. pug와 ejs를 써봤는데 ejs 문법이 편해서 ejs를 사용하기로 했다.
작성한 ejs 파일은 다음과 같다. <%= >안에 서버에서 지정한 변수의 값이 들어간다.
예를 들어 nickname이 고길동이면 "안녕하세요. 고길동님!" 이런식으로 렌더링이된다.
작성한 코드는 다음과 같다.
ejs 템플릿 엔진 안에 <style> 태그로 CSS를 지정하였다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Verification</title>
<style>
body {
font-family: 'Pretendard', Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
line-height: 1.6;
text-align: center;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.logo {
text-align: center;
margin-bottom: 50px;
}
h1 {
color: #5080E1;
margin-bottom: 20px;
}
.highlight-black {
color: #333;
}
.gray-box {
background-color: #F8F8F8;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
display: inline-block;
}
.gray-box .email {
color: #5080E1;
font-weight: bold;
}
.button {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
color: white;
background-color: #5080E1;
text-decoration: none;
border-radius: 5px;
margin: 10px 0;
}
.footer {
margin-top: 20px;
text-align: center;
font-size: 12px;
color: #aaa;
}
</style>
</head>
<body>
<div class="container">
<div class="logo">
<img src="https://caugannies.s3.ap-northeast-2.amazonaws.com/logo/email-logo.png" alt="gannies_logo">
</div>
<h1>이메일 인증<span class="highlight-black">이 필요합니다.</span></h1>
<p>안녕하세요. <%= nickname %>님!</p>
<p>회원가입 완료를 위한 이메일 인증이 필요합니다.</p>
<div class="gray-box">
<span class="email"><%= email %></span>
</div>
<p><span class="highlight-black">아래의 버튼을 클릭하여, 이메일을 인증해주세요.</span></p>
<p><span class="highlight-black">버튼을 클릭하시면, 중간이들 사이트의 메인 페이지로 이동합니다.</span></p>
<a href="<%= emailVerificationLink %>" class="button">메인 페이지로 가기</a>
<div class="footer">
<p>회원가입 인증용 메일</p>
</div>
</div>
</body>
</html>
하지만 Nodemailer로 인증 이메일 발송 결과는 다음과 같았다.
- 이메일 인증 링크는 정상적으로 동작함.
- CSS 파일이 깨짐.
템플릿엔진과 정적파일 경로를 잘못 잡아주면 서버에서 인식을 못한다.
100% 내가 실수를 한 것 같아 이와 관련된 코드를 확인해보았다.
시도한 방법은 아래와 같다.
- useStaticAssets 메서드로 ejs가 속해 있는 폴더의 경로를 설정한다.
- CSS 파일을 별도로 만들고 public 폴더 안에 넣어 경로를 설정한다.
- ejs에 CSS를 인라인 스타일로 지정한다.
1. useStaticAssets 메서드로 ejs가 속해 있는 폴더의 경로를 설정한다.
이러면 <style> 태그 안의 CSS가 인식될 것 같지는 않지만 한번 해보았다.
현재 main.ts는 다음과 같았다.
Thunder Client로 메일을 발송하였다.
메일은 잘 전송되었다.
CSS는 적용되지 않았다.
<style> 태그 안에 CSS를 넣으면 정적 파일 경로를 설정해 놓아도 인식이 되지 않는다.
2. CSS 파일을 별도로 만들고 public 폴더 안에 넣어 경로를 설정한다.
express의 static 미들웨어를 통해 정적 파일 경로를 설정해주었다.
현재 서버 URL이 localhost:3000이기 때문에 localhost:3000/public 안에서 서버가 정적파일을 찾는다.
NestJS에서 정적파일 경로 설정해주는 방법
1) ServceStaticModule 사용
2) Express의 static 미들웨어 사용
1) ServceStaticModule 사용
https://docs.nestjs.com/recipes/serve-static
Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea
docs.nestjs.com
이 설정은 애플리케이션이 client 디렉토리 내의 정적 파일을 제공하도록 한다.
위의 예시에서는 client 디렉토리에 있는 정적 파일은 URL을 통해 접근한다.
보통 정적파일(이미지, CSS 등) 폴더의 이름은 'public'으로 설정한다.
2) Express의 static 미들웨어 사용
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as path from 'path';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 정적 파일 경로를 설정
// 현재 위치가 src/main.ts라고 가정하면 최상위/public 폴더 안에 있는 경로에서 정적파일을 찾음
app.useStaticAssets(path.join(__dirname, '..', 'public'));
await app.listen(3000);
}
bootstrap();
정적 파일 제공 설정
- app.useStaticAssets를 사용한다.
- 주의할 점은 템플릿 엔진 파일에서 정적 파일을 참조할 때는 해당 폴더를 제외한 경로를 사용한다.
예: 최상위/public 을 참조한 경우
app.useStaticAssets(join(__dirname, '..', 'public'));
템플릿엔진에서 참조할 때는 public 폴더를 제외 : <link rel="stylesheet" href="/css/style.css">
템플릿 엔진 설정
- app.setBaseViewsDir : 템플릿 파일의 디렉토리를 설정한다.
- app.setViewEngine : 사용할 템플릿 엔진의 종류를 지정
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 템플릿 엔진 설정
app.setBaseViewsDir(path.join(__dirname, '..', 'views')); // 템플릿 파일 디렉토리 설정
app.setViewEngine('ejs'); // 사용할 템플릿 엔진 설정
await app.listen(3000);
}
bootstrap();
위 경우에는 최상위 경로 안의 views 폴더에 있는 ejs파일이 템플릿 엔진이 된다.
css 파일을 만들어서 분리해주었다.
localhost:3000/css/sign-up-email.css을 입력하면 해당 내용이 출력이 된다.
이러면 서버에서 잘 인식을 한다는 말이다.
ionsHandler] ENOENT: no such file or directory, stat 'C:\Users\airyt\Gannies_BackEnd-3\public\index.html'
Error: ENOENT: no such file or directory, stat 'C:\Users\airyt\Gannies_BackEnd-3\public\index.html'
만약 localhost:3000/public/css/sign-up-email.css 을 입력하면 다음과 같은 에러가 난다.
위에서 말했듯이 정적 파일 경로를 참조할 때는 주의해야한다.
메일을 발송하였더니 CSS가 적용되어 나오지 않았다.
서버에서는 인식하지만 정작 템플릿엔진에는 적용되어 나오지 않았다.
3. ejs에 CSS를 인라인 스타일로 지정한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Verification</title>
</head>
<body style="font-family: Arial, sans-serif; background-color: #f4f4f4; color: #333; line-height: 1.6; text-align: center; margin: 0; padding: 0;">
<div style="max-width: 600px; margin: 20px auto; padding: 20px; background-color: #fff; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); text-align: center;">
<div style="margin-bottom: 50px; text-align: center;">
<img src="https://caugannies.s3.ap-northeast-2.amazonaws.com/logo/email-logo.png" alt="gannies_logo" style="max-width: 100%; height: auto;">
</div>
<h1 style="color: #5080E1; margin-bottom: 20px; text-align: center;">
이메일 인증<span style="color: #333;">이 필요합니다.</span>
</h1>
<p>안녕하세요. <%= nickname %>님!</p>
<p>회원가입 완료를 위한 이메일 인증이 필요합니다.</p>
<div style="background-color: #F8F8F8; padding: 10px; border-radius: 5px; color: #333; margin: 10px 0; display: inline-block; text-align: center;">
<span style="color: #5080E1; font-weight: bold;"><%= email %></span>
</div>
<p><span style="color: #333;">아래의 버튼을 클릭하여, 이메일을 인증해주세요.</span></p>
<p><span style="color: #333;">버튼을 클릭하시면, 중간이들 사이트의 메인 페이지로 이동합니다.</span></p>
<a href="<%= emailVerificationLink %>" style="display: inline-block; padding: 10px 20px; font-size: 16px; color: white; background-color: #5080E1; text-decoration: none; border-radius: 5px; margin: 10px 0; text-align: center;">메인 페이지로 가기</a>
<div style="margin-top: 20px; text-align: center; font-size: 12px; color: #aaa;">
<p>회원가입 인증용 메일</p>
</div>
</div>
</body>
</html>
ejs에 인라인 스타일로 적용하니까 CSS가 적용되어 잘 나왔다.
pretendard 글꼴은 지원하지 않아서 적용해도 다 깨져서 나와서 font-family: Arial, san-serif로 변경해주었다.
요약
문제: NestJS 프로젝트에서 이메일 발송 시 CSS 스타일이 제대로 적용되지 않음.
시도한 방법:
- 템플릿 엔진 내 스타일 태그: CSS를 직접 넣었지만, 렌더링되지 않음.
- 외부 CSS 파일: public 폴더에 넣고 경로를 지정했지만, 이메일에서는 불러와지지 않았음.
- 인라인 스타일: 이메일 클라이언트 호환성 문제로 일부 스타일이 제대로 적용되지 않았음.
최종 해결 방안:
- 인라인 스타일: 이메일 클라이언트 호환성을 위해 인라인 스타일을 사용함.
- 폰트 변경: 이메일 클라이언트에서 지원하는 폰트로 변경함.
결론 : 템플릿엔진을 메일로 발송해야할 경우 CSS은 인라인 스타일을 쓰자.