일기 slecs

개인 블로그 관리 시스템 뼈대를 처음부터 직접 설계해 띄운 이야기

목차

새 프로젝트를 처음부터 차려 올렸다. 개인 블로그 운영용 관리 시스템 - 거창할 것 없는 규모지만, 막상 첫 커밋까지 오는 데 생각보다 손을 많이 탔다. "어차피 나만 쓸 건데"라고 대충 시작하다 며칠 뒤 스파게티 코드가 된 경험이 있어서, 이번엔 뼈대부터 제대로 다지기로 했다.

프로젝트 초기화의 작은 신중함

initial commit이라고 해서 아무 파일이나 던지는 건 아니다. 1인 개발이고 당장 나만 쓸 프로젝트라도, 나중에 코드를 공개하거나 같이 손댈 사람이 생길 수 있다. 그 가능성이 1%여도 처음부터 정리해두는 게 낫다. 귀찮아서 안 한 게 나중에 기술 부채로 돌아오는 건 한 번 경험해봤으면 안다.

그래서 .env.example.gitignore를 제일 먼저 준비했다.

.env.example은 실제 값이 들어있지 않다. 어떤 키 이름이 필요한지 알려주는 문서 역할이다. 팀 온보딩에서 "이 환경변수는 뭐예요?"라는 질문이 안 나오게 해주는, 거의 공짜 문서화다. 몇 달 뒤의 나 자신도 낯선 사람이나 다름없으니, 그냥 쓰는 셈이다.

# .env.example
SECRET_KEY=your-secret-key-here
DATABASE_URL=sqlite:///blog.db
ADMIN_PASSWORD=change-this

실제 .env는 당연히 .gitignore에 박아두었다. DB 비밀번호나 서명 토큰 같은 게 커밋 히스토리에 한 번 들어가면, 레포를 private으로 돌린다고 해도 안심하기 어렵다. git filter-repo로 히스토리를 통째로 재작성하는 사태를 피하려면 처음부터 차단하는 게 맞다. .gitignore에서 빠뜨리기 쉬운 항목도 같이 정리해뒀다.

  • __pycache__/, *.pyc - Python 바이트코드, 자동 생성됨
  • .env, *.local - 환경 변수 파일
  • instance/ - Flask 인스턴스 폴더, SQLite db 파일이 여기 들어감
  • .DS_Store - macOS에서 개발한다면 빠뜨리지 말 것

이 네 줄 챙기는 데 2분도 안 걸린다. 안 챙기면 나중에 훨씬 더 걸린다.

백엔드와 프론트엔드 구조 설계

app/main.py를 진입점으로 두고, app/ 디렉토리 아래로 모듈을 모으는 구조를 잡았다. 플라스크 기준으로 블로그 운영에 필요한 기능은 크게 두 가지다 - 포스트 CRUD와 관리자 인증. 그 이상은 당장 필요 없다.

파일/디렉토리 역할
app/main.py 앱 팩토리, 라우팅 진입점
app/templates/ Jinja2 HTML 템플릿
app/static/style.css 전역 스타일시트
.env.example 환경변수 명세 문서
.gitignore 버전 관리 제외 목록

초기 구조를 이렇게 잡아두면 나중에 app/models.py, app/routes/ 같은 모듈을 추가할 때 자연스럽게 붙는다. 반대로 처음에 main.py 하나에 다 쑤셔 넣으면, 라우트가 10개 넘어갈 때 분리하는 작업이 생각보다 피곤하다. 어떤 함수가 뭘 참조하는지 일일이 추적하면서 파일을 쪼개야 한다. 시작할 때 30분 투자하면 나중에 몇 시간을 번다.

프론트엔드는 CSS 최소화로 시작했다. 개인 블로그 관리 툴이라 화려할 필요가 없고, 처음부터 Tailwind나 Bootstrap 같은 걸 셋업하면 정작 기능 만들 시간이 줄어든다. 나중에 언제든 붙일 수 있다. 초기엔 기능 완성이 우선이다. 디자인은 기능이 다 돌아간 다음에 손봐도 늦지 않는다.

관리자 페이지 분리와 initial commit이 담는 것

admin_dashboard.htmladmin_post_form.html을 처음부터 템플릿으로 준비했다. 혼자 쓸 블로그인데 이렇게까지 나눌 필요가 있냐 싶겠지만, 이유가 있다.

포스트 목록을 보는 화면과 포스트를 작성하는 화면은 역할이 다르다. 하나의 템플릿에 조건부로 다 욱여넣으면 HTML 자체가 복잡해지고, 수정할 때 어디를 건드려야 할지 한눈에 안 들어오게 된다. 거기다 라우트 구조가 자연스럽게 따라온다는 장점도 있다. /admin/ → 대시보드, /admin/post/new → 작성 폼. 이 구조를 머릿속에 그려두고 템플릿을 나눠두면, 라우트 구현할 때 흐름이 깔끔하다.

# 염두에 둔 대략적인 구조
@app.route('/admin/')
def admin_dashboard():
    posts = Post.query.order_by(Post.created_at.desc()).all()
    return render_template('admin_dashboard.html', posts=posts)

@app.route('/admin/post/new', methods=['GET', 'POST'])
def admin_post_new():
    if request.method == 'POST':
        # 저장 로직
        pass
    return render_template('admin_post_form.html')

나중에 관리자 로그인 기능을 추가할 때도 편하다. /admin/ 하위 라우트 전체에 @login_required 데코레이터 하나로 묶을 수 있다. 섞여 있으면 하나씩 붙여야 한다. 초기 설계에서 관심사를 분리해두면, 이런 확장이 자연스럽게 따라온다.

스캐폴딩 도구를 쓰면 10초 만에 파일 구조가 생긴다. 근데 그렇게 생성된 구조는 범용적이라서 이 프로젝트가 뭘 하려는지 의도가 담기지 않는다. 손으로 차린 initial commit은 다르다. 어떤 파일이 먼저 필요한지, 어디에 뭘 두기로 결정했는지가 그 자체로 설계 의도다. 나중에 코드를 보는 사람 - 팀원이든 미래의 나든 - 이 "왜 이렇게 나눠놨지?"를 파악하는 데 훨씬 빠르다.

개인 프로젝트일수록 이 규율이 더 필요하다고 느낀다. 팀 프로젝트면 코드 리뷰나 컨벤션 강제가 어느 정도 구조를 잡아주는데, 혼자 하면 그런 외부 압력이 없다. 흐지부지 시작하면 빠르게 무너진다. 처음 30분을 더 투자해서 skeleton을 다져놓으니 지금 기분이 훨씬 낫다.

다음은 main.py에 실제 라우트를 구현하고 SQLite를 붙이는 단계다. 템플릿들도 기능이 들어갈수록 살이 붙을 것 같다. 규모는 작지만 의도가 담긴 시작이다.

댓글 0

첫 댓글 달아줘.