왜 커스텀 빌드를 선택했는가?
CKEditor5는 CDN 방식으로도 사용할 수 있다.
하지만 이미지 업로드를
서버(JSP/Servlet)와 연동하려고 하면
CDN 방식은 생각보다 제약이 많다.
CDN Super-build의 한계
❌ 불필요한 플러그인이 과도하게 포함됨
❌ 업로드 어댑터를 직접 제어하기 어려움
❌ 서버 연동 시 에러 원인 파악이 힘듦
❌ JSP 환경에서는 디버깅 난이도가 높음
실제로 CDN 방식으로 여러 번 시도했지만,
오류를 해결하는 데 드는 시간이 너무 컸다.
커스텀 빌드를 선택한 이유
⭕ 필요한 플러그인만 선택 가능
⭕ SimpleUploadAdapter 사용 가능
⭕ JSP / Servlet 구조에 맞게 설계 가능
그래서 CKEditor5 커스텀 빌드를 기준으로 진행했다.
(CDN은 너무 많은 오류를 만나서 포기했다…)
1. 전체 구현 구조 한눈에 보기
[Browser]
↓ (multipart/form-data)
CKEditor5
↓ POST /uploadImage.do
FrontController (@MultipartConfig)
↓
UploadImageAction
↓
/upload 폴더에 이미지 저장
↓
{ "url": "/day042_totalpractice/upload/xxx.png" } 응답
에디터 + 서버 업로드 + FrontController 구조를
끝까지 연결하는 것이다.
2. CKEditor5 커스텀 빌드 생성
2-1. Node / Git 준비
1) Git 설치부터 시작
CKEditor5 커스텀 빌드는
GitHub 저장소를 기반으로 한다.
따라서 Git 설치는 필수다.
▼ Git 다운로드
👉 https://git-scm.com/download/win
(접속 시 자동으로 Windows 버전 다운로드)
설치 과정은 대부분 Next만 눌러도 되지만,
아래 설정은 반드시 체크되어 있어야 한다.
✔ Use Git from the command line and also from 3rd-party software
✔ Use bundled OpenSSH
✔ Checkout Windows-style, commit Unix-style line endings
기본값 그대로
Next → Next → Install 하면 문제 없다.

2) 설치 후 Git 동작 확인
CMD를 열고 아래 명령어를 입력한다.
git --version
버전 정보가 출력되면 Git 설치는 정상이다.

3) Node.js 설치
CKEditor5 빌드는 Node.js 환경에서 동작한다.
Node 설치 시 npm은 자동으로 함께 설치된다.
이번 작업에서는 아래 버전을 사용했다.
: node-v18.20.8-x64.msi

설치 후 CMD에서 확인한다.

둘 다 출력되면 환경 준비 완료.
2-2. CKEditor5 커스텀 빌드 생성
1) 작업 폴더 생성
mkdir C:\ckeditor-build
cd C:\ckeditor-build
커스텀빌드가 생성될 폴더를 만들고
폴더명(=경로)을 입력하자.
2) 커스텀 빌드 생성 명령
npx create-ckeditor5-build
위 명령어를 입력하면 폴더에 파일이 생성된다.
이제 커스텀 빌드 기초 파일 제작이 완료되었다.
3. SimpleUploadAdapter 기반 커스텀 설정
설정 핵심 포인트
- Base64 방식 미사용
- 이미지 파일을 multipart/form-data로 서버에 직접 업로드
- 서버는 { "url": "이미지경로" } 만 반환
▼ ckeditor.js 핵심 구성
import SimpleUploadAdapter
from '@ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter';
ClassicEditor.builtinPlugins = [
Image,
ImageToolbar,
ImageUpload,
SimpleUploadAdapter,
...
];
ClassicEditor.defaultConfig = {
toolbar: {
items: [
'bold', 'italic',
'imageUpload',
'undo', 'redo'
]
}
};
빌드 실행 (CMD 입력)
npm run build
위 명령어를 입력하면 내가 설정한 커스텀 설정으로
커스텀 빌드 파일을 제작한다.

build 폴더에 ckeditor.js 파일 생성 완료!
이제 해당 파일(=커스텀 빌드)을 프로젝트에 사용하면 된다.
4. JSP 프로젝트에 ckeditor.js 적용
파일 위치 (중요)
src/main/webapp/js/ckeditor.js
생성된 커스텀빌드(ckeditor.js) 파일을
webapp 폴더에 있는 js폴더에 넣어주자.
JSP에서 로드
<script src="<%= request.getContextPath() %>/js/ckeditor.js"></script>
내가 사용할 jsp 파일 상단에 로드하면 된다.
이 경로 문제로 ClassicEditor is not defined
오류가 가장 많이 발생한다.
5. JSP에서 에디터 초기화 (컨텍스트 경로 핵심)
ClassicEditor
.create(document.querySelector('#editor'), {
simpleUpload: {
uploadUrl: '<%= request.getContextPath() %>/uploadImage.do'
}
})
.then(editor => {
window.editor = editor;
});
JSP에서 request.getContextPath()로
업로드 URL을 덮어써야 안전하다.
6. FrontController 설정 (가장 중요한 포인트)
- multipart 업로드의 핵심
multipart 요청은 실제 요청을 받는
서블릿에만 설정해야 한다
▼ FrontController.java 코드에 추가
@WebServlet("*.do")
@MultipartConfig
public class FrontController extends HttpServlet {
반드시 추가해야 하는 null 처리
ActionForward forward = action.execute(request, response);
if (forward == null) {
return; // 업로드처럼 응답을 직접 처리한 경우
}
이 한 줄이 없으면 NullPointerException 100% 발생
> 이미지 업로드 액션 페이지에선
> forward 없이 바로 return으로 종료되기 때문!
7. UploadImageAction 구현
package controller.board;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import controller.common.Action;
import controller.common.ActionForward;
public class UploadImageAction implements Action {
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) {
try {
request.setCharacterEncoding("UTF-8");
// CKEditor 업로드 필드명은 반드시 "upload"
Part filePart = request.getPart("upload");
if (filePart == null || filePart.getSize() == 0) {
throw new RuntimeException("업로드 파일이 없습니다.");
}
String fileName =
System.currentTimeMillis() + "_" + filePart.getSubmittedFileName();
// 저장 경로 (/upload)
String savePath = request.getServletContext().getRealPath("/upload");
File uploadDir = new File(savePath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 파일 저장
filePart.write(savePath + File.separator + fileName);
// 브라우저 접근 URL
String fileUrl = request.getContextPath() + "/upload/" + fileName;
// CKEditor 규격 JSON 응답
response.setContentType("application/json; charset=UTF-8");
response.getWriter().write("{\"url\":\"" + fileUrl + "\"}");
System.out.println("[이미지 업로드 완료] URL = " + fileUrl);
} catch (Exception e) {
e.printStackTrace();
}
// ★★★★★ 핵심 ★★★★★
// 응답을 직접 처리했으므로 forward 사용 X
return null;
}
}
필드명은 반드시 upload
응답 JSON은 반드시 { "url": "..." }
처리 완료되면 반드시 return null로 끝난다.
8. 발생했던 대표 오류 정리
ClassicEditor is not defined
- ckeditor.js 경로 오류
404 uploadImage.do
- 컨텍스트 경로 누락
500 Internal Server Error
- @MultipartConfig 누락
- forward == null 처리 없음
[ 이미지 업로드 구현 완료 ]
▼ 게시글 작성 페이지에서 이미지 업로드

▼ 게시글 업로드 완료 후 글 상세보기 확인

'개주 훈련일지 > 🏋️ 전집중 호흡 훈련' 카테고리의 다른 글
| CKEditor5 기본 문법 정리 (0) | 2025.12.14 |
|---|---|
| CKEditor5 커스텀 빌드 수정하는법 (0) | 2025.12.14 |
| CKEditor5 기초 가이드 (0) | 2025.12.12 |
| Java로 이메일 발송 구현하기(Gmail SMTP 사용) (0) | 2025.12.11 |
| OAuth 기반 카카오 로그인 API (0) | 2025.12.10 |