원활한 포스팅을 위한 블로그 프로세스 개선하기

7 minute read
2024-07-05

기존 티스토리를 사용하면서 큰 문제는 없었다. 하지만 자잘한 문제가 있었다. 편집 페이지에서 작성하다가 폰트를 수정하는 과정에서 종종 폰트 색상이 회색으로 바뀌는 이슈였다.


뿐만 아니라, 코드를 표시하는데 아쉬움이 컸다. 전체 코드에서 문단에서 말하고자 하는 부분을 강조하기 어려웠다. 주석으로 표시할 수 있었지만 이또한 가시성 별로였다.


마음에 드는 블로그 템플릿을 찾아보았지만 내가 원하는 스타일의 블로그 템플릿을 찾을 수 없어 다시 직접 만드는 걸로 생각이 정리되었다.


그리고 마크다운으로 작성하면, 콘텐츠도 특정 플랫폼에 종속되지 않을 수 있다. 직접 가지고 있기 때문에 나중에 마이그레이션을 한다해도 콘텐츠는 그대로 사용할 수 있다. 필요 시에는 remark 와 같은 도구로 핸들링도 가능하다.


예전에도 여러 번 블로그를 직접 만들고 필요한 기능을 만드는 시간도 많이 소요되었다. 그래서 이번에 만들 블로그는 최소한의 스타일을 목표로 했고 점진적으로 기능들을 추가하기로 결정했다.


여러 블로그들을 살펴보니, 요즘에는 정적 페이지를 만들기 위해 astro 와 react/svelete 로 만드는 걸 확인할 수 있었다. 그래서 동일한 기술 스택을 채택했다.


Dev Stack
- Astro
- React / Typescript
- TailwindCSS


그리고 배포 과정을 손쉽게 하기 위해 vercel 을 이용하였다. 하지만 문제가 있었다. 글쓰기가 매우 불편하다는 점이다.


Markdown Editor

마크다운 에디터를 IDE 에서 작성할 수 있지만, 개인적으론 티스토리 글쓰기 페이지보다 매우 불편했다. (물론 잘 쓰시는 분도 많았다) 이를 개선하고자 에디터를 찾아보다가 Typora 가 눈에 들어왔다. (링크)


image-20240705134333275


깔끔한 UI 도 한몫했다. 마크다운을 위한 간편한 단축키들도 세팅되어 있었다. 그리고 코드 작성도 자동 indent 를 지원해주었고 그외 글 쓰도록 동기 혹은 느낌를 주었다.


뿐만 아니라, 이미지도 손쉽게 드래그앤드랍이 가능했다. 이미지를 저장하는 경로도 설정 가능해서 매우 만족스러웠다.


image-20240705134728251


이미지 삽입할 때 경로를 지정할 수 있다. 현재 프로젝트 구조는 아래와 같아서 위처럼 설정했다.


기본적으로 정적 리소스를 public 하위에 위치시키기 때문에 규칙을 글 제목과 동일한 디렉토리를 public/blog 하위에 위치시켰다.


project struct
blog
├── public
│   └── blog
│       ├── # ...
│       └── my-post-title
│           └── image-01.jpeg

└── src
    └── content
        └── blog
            └── my-post-title.md


하지만 배포하고 나니 이미지가 보이지 않는 문제가 발생했다. 에디터에서 지정한 경로로 img 태그의 src 가 그대로 설정되서 발생한 문제였다.


The Problem of img path
<img
     src="../../../public/[__post_title__]/image-01.jpeg"
/>


이미지 경로를 빌드타임에서 처리

글 작성 시에는 드래그앤드랍한 기준으로 지정해서 에디터에서 정상적으로 보여주도록 했고


image-20240705140141652


배포 후에는 아래와 같은 형식으로 수정해야했다.


<img
	src="/blog/blog-dev-log/image-20240705140141652.png"
/>


이를 해결하기 위해 빌드 시 이미지 경로에 대한 처리가 필요했다. astro 는 astro.config.mjs 설정 스크립트에서 custom plugin 으로 처리할 수 있었다.


astro.config.mjs
export default defineConfig({
  // ...
  markdown: {
    remarkPlugins: [
      transformImagePaths,
    ],
  },
  // ...
})
 
import { visit } from 'unist-util-visit';
 
function transformImagePaths() {
  return (tree) => {
      const publicIndex = node.url.indexOf('public');
      if (publicIndex !== -1) {
        const imgRelativePath = node.url.substring(
          publicIndex + 'public'.length
        );
        node.url = imgRelativePath;
      }
    });
  };
}


내가 작성한 markdown 을 조작하기 위해서는 remark 가 만들어내는 AST (추상 구문 트리) 기반으로 처리해야한다.


substring 처리 : 이미지의 경로에 public 이 포함 된 경우, 최초 ‘public’ 문자가 나오는 위치를 기준으로 문자를 나눈다.


'../../../public/blog/my-post-title/image-01.jpeg'
-> 
[
  '../../../',
  '/blog/my-post-title/image-01.jpeg'
]


node.url 재할당 : 이미지 경로를 정제한 경로로 바꿔준다.


위와 같은 방식으로 처리해주면 배포 후에도 정상적으로 이미지가 보인다.


개행 문제

마크다운에서 입력했던 개행은 html 로 변환 후에는 반영되지 않는다. 내가 원하는 건 heading 태그마다 다른 개행되도록 (h1, h2 태그에만 개행 추가) 하고 싶었다.


이 문제도 custom plugin 을 추가해서 해결하였다.


add-heading-plugin
export default defineConfig({
  // ...
  markdown: {
    remarkPlugins: [
			remarkHeading,
    ],
  },
  // ...
})
 
function remarkHeading() {
  return (tree) => {
    visit(tree, 'heading', (node) => {
      switch (node.depth) {
        case 1:
        case 2: {
          node.children.push({
            type: 'html',
            value: generateBrByCount(2),
          });
          break;
        }
        default:
          break;
      }
    });
  };
}
 
function generateBrByCount(count = 1) {
  return '<br />'.repeat(count);
}


더 나아가 문단, 이미지 혹은 코드도 개행을 추가하고 싶어서 아래 플러그인도 추가해주었다.


other plugins
function remarkAddBreaks() {
  function isTargetNode(node) {
    const TARGET_LIST = ['paragraph', 'image'];
    return TARGET_LIST.includes(node.type);
  }
  return (tree) => {
    visit(tree, (node) => {
      if (isTargetNode(node)) {
        if (!node.children) {
          node.children = [];
        }
        node.children.push({
          type: 'html',
          value: generateBrByCount(3),
        });
      }
    });
  };
}
 
 
function remarkEnhanceCodeBlocks() {
  return (tree) => {
    visit(tree, 'code', (node, index, parent) => {
      if (parent && Array.isArray(parent.children)) {
        parent.children.splice(index + 1, 0, {
          type: 'html',
          value: generateBrByCount(2),
        });
      }
    });
  };
}


마무리

지금까지의 일련의 과정을 통해서 현재 블로그를 만들었다. 무엇보다 글쓰는 과정이 나에게 최적화(?) 되어 있어 글을 더 쓰고 싶게 만들어 주는 점이 큰 것 같다.


#productivity