#5 react-markdown 적용하기

react-markdown을 이용해서 markdown 포스트를 스타일링하자!


Table Of Contents


포스트 내용 불러오기


이전 포스트와 같은 방법으로 loader를 이용해서 content를 불러온다.

현재 라우팅은 /subBlog/${postId}처럼 하고 있고, 나중에는 subBlog 자리에 서브 블로그 id 등을 넣어서 서브 블로그마다 다른 카테고리를 로드할 예정이다.

export const loader = async ({ context, params }: LoaderArgs) => { const supabase = createClient<Database>( context.env.SUPABASE_URL, context.env.SUPABASE_KEY ); const loadData = async (id: string) => { try { const { data: postData, error: postError } = await supabase .from("posts") .select("content") .eq("id", id) .single(); if (postError) throw new Error(); return postData?.content; } catch (err) { return null; } }; const postId = params.postId; if (!postId) { return null; } const data = await loadData(postId); return data; };

loader 안에서 params를 이렇게 불러올 수 있다. subBlog.$postId.tsx에서 사용하는 loader이기 때문에 postId가 없는 경우는 없을 것이라고 생각하는데, if문을 넣지 않으면 params.postId가 undefined일 수도 있다는 경고가 떠서 저렇게 두었다. 나중에 공부해봐야지…

그러면 아래처럼 content가 보인다.

div로 보여줌

지금은 그냥 div 태그에 때려박아서 읽기가 힘들다🥰

react-markdown 적용하기


react-markdown 설치

이제 글을 읽기 쉽게 보여주자.

모든 글은 markdown(혹은 mdx) 형식이기 때문에 react-markdown을 이용한다. React에서 사용되는 markdown 관련 라이브러리 중에서 다운로드 수가 제일 많기에 선택했다.

  • 자세한 사용법은 여기를 참고하자.

우선 npm으로 설치부터 해 준다.

npm install react-markdown

사용법은 간단하다.

import ReactMarkdown from "react-markdown";

처럼 import해주고,

<div>{content}</div>

이랬던 코드를

<ReactMarkdown>{content}</ReactMarkdown>

이렇게 감싸주면 된다.

1차 스타일

링크나 리스트들이 잘 적용된 모습이다. 나머지는 우리가 스타일링해 주어야 한다.

스타일링

스타일링도 간단하다.

<ReactMarkdown components={{ a: (props: any) => ( <a target="_blank" css={{ color: "red" }} {...props} /> ), }} > {content} </ReactMarkdown>

이렇게 components props를 넣고, 그 안에 원하는 태그의 렌더링 방법을 정해주면 된다.

실험적으로 a 태그의 텍스트 색상을 빨간색으로 지정해 보았다.

a태그를 빨간색으로 테스트

잘 적용된다!!

우선 아래처럼 꾸며줬다. 세세한 디자인은 나중에 적용하…겠지…?

2차 스타일

코드 스타일링하기

포스팅 맨 아래 부분을 보면 코드가 있는데, 지금은 코드 하이라이팅이 적용되어 있지 않다. 이전 블로그 처럼 syntax highlighter를 사용해서 하이라이팅을 해 주겠다.

이전 블로그

하이라이팅은 react-syntax-highlighter 라이브러리를 이용한다.

  • 라이브러리에 대한 설명은 여기
  • react-markdown에 적용하는 방법은 여기

우선 설치부터 해 준다.

npm install react-syntax-highlighter

앞에서 components props에 다른 태그들을 스타일링한 것처럼, 코드도 이렇게 스타일링 해주면 된다.

import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { nord } from "react-syntax-highlighter/dist/cjs/styles/prism"; ... code : (props: any) => { const match = /language-(\w+)/.exec(props.className || ""); return !props.inline && match ? ( <SyntaxHighlighter children={String(props.children).replace(/\n$/, "")} style={nord} language={match[1]} PreTag="div" {...props} /> ) : ( <code css={styledCode} {...props}> {props.children} </code> ); };

const match = /language-(\w+)/.exec(props.className || "");는 코드가 어떤 언어로 작성되었는지를 파싱하는 코드이다. ``` 뒤 혹은 ~~~ 뒤에 지정한 언어를 알아내는 것이다. 또한, props중에 inline코드인지 아닌지를 알려 주는 플래그가 있기 때문에 이들을 이용하여 inline 코드와 그렇지 않은 코드를 다르게 스타일링 할 수 있다.

코드 스타일 적용

파란색 배경이 인라인 코드 부분이다.

Latex 스타일링하기

latex 스타일 적용 전

Latex 문법을 일부 적용시켜 보았다. 물론 지금은 따로 옵션을 주지 않았기 때문에 문법에 맞게 파싱되지는 않는다.

설명에 따라 아래 패키지들을 설치하고 적용해주자.

npm install remark-math rehype-katex
import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; import "katex/dist/katex.min.css"; ... <ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]} ... />

💥 css 적용 문제와 Remix 스타일링

latex 스타일 적용 실패

아… 왜이럴까…

글씨가 두 번씩 나와서 개발자 도구로 찍어봤는데 aria-hidden=true임에도 불구하고 글씨가 계속 보이고 있다.

개발자도구로 디버깅

아마도 아래 css import가 제대로 먹히지 않은 것 같다. 다시 보니 NNN도 글씨가 수상하게 작다.

import "katex/dist/katex.min.css";

Remix의 styling 가이드를 읽으니 Remix에서 스타일링을 하기 위해서는 을 추가해주어야 한다고 한다. Remix에서는 links를 export해서 stylesheet를 추가할 수 있다고 한다.

import styles from "katex/dist/katex.min.css"; export function links() { return [{ rel: "stylesheet", href: styles }]; }

이렇게 바꿔주니 잘 작동한다!

latex 스타일 적용 성공

remark-gfm 적용하기

* Lists * [ ] todo * [x] done A table: | a | b | | - | - |

같은 문법들을 이용하기 위해서는 remark-gfm을 이용해야 한다.

같은 방법으로 설치해주고, 적용해주자. 나는 이미 remarkMath가 적용되어 있기 때문에 그 옆에 써주었다. 만약 remarkMath를 사용하지 않는다면 remarkPlugins={[remarkGfm]}처럼 쓰면 된다.

npm install remark-gfm
import remarkGfm from "remark-gfm"; remarkPlugins={[remarkMath, remarkGfm]}

remark-gfm 적용

역시 잘 적용된다.