글자 수 세기, AI로 10분 만에 직접 만들어 쓰기
글자 수, 단어 수, 원고지 매수를 실시간으로 계산하는 도구를 직접 만들었습니다. 외부 API 없이 순수 JavaScript로 완성한 과정을 공유합니다.
위 화면은 이 포스트의 프롬프트를 AI에 입력해서 생성한 샘플입니다
글자 수 세기, 언제 필요할까?
글을 쓰는 사람이라면 글자 수가 중요한 순간이 반드시 찾아옵니다.
블로그: 포스트가 너무 짧으면 검색 노출이 불리합니다. 대부분의 SEO 가이드는 최소 1,000자 이상을 권장합니다.
SNS: 트위터(X)는 280자, 인스타그램 캡션은 2,200자 제한이 있습니다. 최적 길이를 파악하고 쓰는 것이 중요합니다.
원고: 출판사나 잡지에 원고를 제출할 때 "200자 원고지 10매" 같은 기준을 씁니다.
자기소개서: 항목별 글자 수 제한이 있고, 딱 맞게 써야 합니다.
이런 상황마다 글자 수를 확인하기 위해 다른 사이트로 이동하는 건 번거롭습니다. 직접 만들었습니다.
완성된 글자 수 세기 살펴보기
위의 라이브 데모에서 직접 써보세요.
텍스트를 입력하면 8가지 통계가 실시간으로 업데이트됩니다.
| 통계 | 설명 |
|---|---|
| 전체 글자 수 | 공백 포함 모든 문자 |
| 공백 제외 | 실질적인 글자 수 |
| 단어 수 | 공백 기준으로 나눈 단어 |
| 문장 수 | . ! ? 기준 |
| 문단 수 | 빈 줄 기준 |
| 읽기 시간 | 분당 300자 기준 |
| 200자 원고지 | 원고지 매수 |
| 400자 원고지 | 원고지 매수 |
AI에게 어떻게 요청했나?
1단계 프롬프트 — 기본 카운터
Next.js 14 + TypeScript + Tailwind CSS로 글자 수 세기 컴포넌트를 만들어줘.
요구사항:
- 'use client' 컴포넌트
- 큰 textarea (min-height: 200px)
- 실시간으로 계산:
- 전체 글자 수 (text.length)
- 공백 제외 글자 수 (text.replace(/s/g, '').length)
- 단어 수 (text.trim().split(/s+/).filter(Boolean).length)
- 문장 수 (text.split(/[.!?]+/).filter(s => s.trim()).length)
- 문단 수 (text.split(/
s*
/).filter(Boolean).length)
- 읽기 예상 시간 (공백 제외 글자 ÷ 300)
- 200자 원고지 매수 (공백 제외 글자 ÷ 200, 올림)
- 400자 원고지 매수 (공백 제외 글자 ÷ 400, 올림)
- 통계를 카드 그리드로 표시 (2열 또는 4열)
2단계 프롬프트 — 편의 기능
다음을 추가해줘:
1. 클립보드 붙여넣기 버튼 (navigator.clipboard.readText())
2. 초기화(지우기) 버튼
3. 복사 버튼 (textarea 내용 전체 복사)
4. 목표 글자 수 설정: 입력하면 진행률 바 표시 (현재/목표)
5. useMemo로 계산 최적화
코드 핵심 포인트
1. 한국어 글자 수 처리
JavaScript의 string.length는 UTF-16 코드 단위를 기준으로 합니다. 한국어 기본 글자(가~힣)는 1개의 코드 단위를 차지하므로 length로 바로 세면 됩니다.
const charCount = text.length // 공백 포함
const charNoSpace = text.replace(/s/g, '').length // 공백 제외
이모지는 여러 코드 단위를 차지할 수 있습니다. 정확하게 세려면:
// 이모지 포함 정확한 글자 수
const charCountAccurate = [...text].length // spread로 유니코드 분리
2. 단어 수 계산
한국어는 띄어쓰기 기준으로 단어를 구분합니다.
const wordCount = text.trim()
? text.trim().split(/s+/).length
: 0
// 영어 혼용 텍스트에서도 정확하게 동작
// "Hello 안녕하세요 World" → 3단어
3. 문장 수 계산
const sentenceCount = text
.split(/[.!?。!?]+/) // 영문, 한문 구두점 모두 포함
.filter(s => s.trim().length > 0)
.length
4. useMemo로 최적화
텍스트가 바뀔 때마다 8개 계산을 모두 재실행합니다. useMemo로 불필요한 재계산을 방지합니다.
const stats = useMemo(() => {
const charTotal = text.length
const charNoSpace = text.replace(/s/g, '').length
const words = text.trim() ? text.trim().split(/s+/).length : 0
const sentences = text.split(/[.!?。!?]+/).filter(s => s.trim()).length
const paragraphs = text.split(/
s*
/).filter(Boolean).length
const readingTime = Math.ceil(charNoSpace / 300)
const manuscript200 = Math.ceil(charNoSpace / 200)
const manuscript400 = Math.ceil(charNoSpace / 400)
return { charTotal, charNoSpace, words, sentences, paragraphs,
readingTime, manuscript200, manuscript400 }
}, [text]) // text가 바뀔 때만 재계산
글자 수별 콘텐츠 가이드
블로그 포스트
| 글자 수 | 적합한 콘텐츠 |
|---|---|
| 500자 이하 | 짧은 메모, 공지 |
| 500~1,000자 | 기본 포스트 (SEO 최소 기준) |
| 1,000~2,000자 | 일반 블로그 포스트 |
| 2,000~4,000자 | 상세 튜토리얼, 가이드 |
| 4,000자 이상 | 심층 분석, 전문 콘텐츠 |
Google AdSense 심사를 위한 권장 글자 수는 공백 제외 1,500자 이상입니다.
SNS별 최적 길이
트위터(X): 71100자 — 짧고 핵심만
인스타그램 캡션: 138150자 — 이미지 설명 + 해시태그
LinkedIn: 1,0001,300자 — 전문적인 인사이트
네이버 블로그: 2,0004,000자 — 정보성 콘텐츠
원고지 환산
200자 원고지 1매 = 공백 제외 200자
→ A4 기준 약 반 페이지
400자 원고지 1매 = 공백 제외 400자
→ A4 기준 약 한 페이지
10매 분량 = 공백 제외 2,000~4,000자
활용 예시
자기소개서 1,000자 제한 항목에 딱 맞게 작성할 때 실시간으로 확인하면서 씁니다.
블로그 포스트 체크 완성한 포스트를 붙여넣으면 AdSense 기준(1,500자)을 충족했는지 바로 확인합니다.
번역 의뢰 전 분량 확인 번역 비용은 보통 글자 수나 단어 수로 계산합니다. 의뢰 전에 정확한 분량을 파악합니다.
소설/시나리오 진행률 관리 목표 글자 수를 설정하고 진행률 바로 현재 위치를 확인합니다.
이렇게 확장할 수 있습니다
- 언어 감지: 한국어/영어 자동 감지 후 분당 읽기 속도 다르게 적용
- 키워드 밀도: 특정 단어가 전체 글에서 몇 % 차지하는지 계산
- 가독성 점수: 문장 평균 길이 기반 가독성 점수
- 내보내기: txt/docx 파일로 저장
- 자동 저장: localStorage로 임시 저장
자주 묻는 질문
Q. 이모지를 포함한 텍스트는 어떻게 세나요? 현재 버전은 JavaScript의 기본 length를 사용합니다. 이모지는 2개로 카운트될 수 있습니다. 정확하게 세려면 spread 연산자([...text].length)를 사용합니다.
Q. 붙여넣기가 안 되는 경우? 브라우저 보안 정책으로 clipboard API가 차단될 수 있습니다. 이 경우 직접 Ctrl+V로 붙여넣기 하세요.
Q. 읽기 시간 계산이 정확한가요? 평균 독서 속도(분당 300자)를 기준으로 계산합니다. 개인차가 있어 참고용으로 활용하세요.
AI 뚝딱 공방 시리즈 마무리
이것으로 AI 뚝딱 공방 1기 시리즈가 완성됐습니다!
단위 변환기, 번역기, 환율 계산기, 뽀모도로 타이머, 색상 코드 변환기, 글자 수 세기까지 6개 도구를 만들었습니다.
모든 도구는 도구함에서 바로 쓸 수 있습니다. AI 프롬프트와 코드는 각 포스트에서 확인하세요.
앞으로 2기 시리즈로 더 실용적인 도구들을 만들어나갈 예정입니다.
'use client'
import { useState, useMemo } from 'react'
function calcStats(text) {
const total = text.length
const noSpace = text.replace(/\s/g, '').length
const words = text.trim() === '' ? 0 : text.trim().split(/\s+/).length
const sentences = text.trim() === '' ? 0 : (text.match(/[.!?]+/g) ?? []).length
const paragraphs = text.trim() === ''
? 0
: text.split(/\n\s*\n/).filter(p => p.trim().length > 0).length
return {
total,
noSpace,
words,
sentences,
paragraphs,
readingMin: parseFloat((noSpace / 300).toFixed(1)), // 분당 300자
page200: parseFloat((noSpace / 200).toFixed(1)), // 200자 원고지
page400: parseFloat((noSpace / 400).toFixed(1)), // 400자 원고지
}
}
export function WordCounter() {
const [text, setText] = useState('')
// text 변경 시에만 재계산
const stats = useMemo(() => calcStats(text), [text])
const handlePaste = async () => {
const clipText = await navigator.clipboard.readText()
setText(clipText)
}
return (
<div>
{/* 툴바 */}
<button onClick={handlePaste}>붙여넣기</button>
<button onClick={() => setText('')}>초기화</button>
{/* 입력 영역 */}
<textarea
value={text}
onChange={e => setText(e.target.value)}
placeholder="텍스트를 입력하세요..."
style={{ minHeight: '200px' }}
/>
{/* 통계 카드 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '12px' }}>
<div>📝 {stats.total.toLocaleString()} 전체 글자</div>
<div>✂️ {stats.noSpace.toLocaleString()} 공백 제외</div>
<div>🔤 {stats.words.toLocaleString()} 단어</div>
<div>💬 {stats.sentences.toLocaleString()} 문장</div>
<div>📄 {stats.paragraphs.toLocaleString()} 문단</div>
<div>⏱️ {stats.readingMin}분 읽기 시간</div>
<div>📃 {stats.page200}매 200자 원고지</div>
<div>📋 {stats.page400}매 400자 원고지</div>
</div>
</div>
)
}