Next13 블로그 프로젝트

imgur 이미지 URL 업로드 + ReactQuill에서 URL로 이미지 저장하기

sakuraop 2023. 10. 17. 12:32

목차

1. imgur 회원가입 및 로그인 
2. imgur APP 등록하기
3. input으로 이미지파일 선택하기
4. imgur에 이미지 파일 업로드 POST 요청 보내기
++ 어??? 나는 요청이 안되는데요?
5. ReactQuill 에 ref 속성 주입하기
6. ReactQuill에 URL 로 이미지 삽입하기


1. imgur 회원가입 및 로그인 

https://imgur.com/

위 링크로 이동하여 회원가입을 하고 로그인도 해줍니다.


2. imgur APP 등록하기

https://api.imgur.com/oauth2/addclient

로그인 하고 위 링크로 이동한 뒤에

 

이런 식으로 작성해주면 APP을 생성할 수 있습니다.

 

https://imgur.com/account/settings/apps

APP을 생성한 뒤 위 링크로 접속하면

이렇게 생성한 앱 목록이 있습니다.

 

Client ID를 API 요청 header에 포함하여 보내야하기 때문에 저장해둡니다.


3. input으로 이미지파일 선택하기

    <div className={styles.quillContainer}>
      <input type='file' onChange={onFileChange} />
      <button onClick={onFileUpload} type='button'>upload</button>
      <ReactQuill {...props} />
    </div>

 

저는 이렇게 만들어 보았습니다.

 

업로드할 파일을 저장할 state와 onFileChange 함수를 만듭니다.

input의 onChange 이벤트를 이용하여 이미지파일을 state에 저장하도록 합니다.

  // 이미지파일 state
  const [file, setFile] = useState<any>();

  // onChange 이벤트에 전달하여 setFile로 업로드할 이미지파일을 state에 저장합니다.
  const onFileChange = (e: any) => {
    setFile(e.target.files[0]);
  };
 
      <input type='file' onChange={onFileChange} />

 

파일 선택을 하면 e.target.files[0] 에 저장이 되고, 이를 setState로 저장시켜 줄 수 있습니다.

 

파일이 선택된 모습


4. imgur에 이미지 파일 업로드 POST 요청 보내기

button을 클릭하면 실행할 함수를 만들어 봅시다.

 

imgur에 이미지등록 API 요청을 보내야 합니다. 아래에 순서대로 설명하겠습니다.

  const onFileUpload = async () => {
    const formData = new FormData(); // formData 형식으로 body에 전달합니다.
    formData.append('image', file); // formDate에 이미지 파일을 추가합니다.

    const res = await fetch('https://api.imgur.com/3/image', {
      method: 'POST',
      headers: {
        Authorization: `Client-ID ${process.env.NEXT_PUBLIC_IMGUR_CLIENT_ID}`,
        Accept: 'application/json',
      },
      body: formData,
    });

    const result = await res.json(); // imgur 업로드 결과 데이터
    const { link } = result.data; // 이미지 링크
  };

//...
      <button onClick={onFileUpload} type='button'>upload</button>

 

https://apidocs.imgur.com/

위는 imgur API 레퍼런스입니다. 업로드 이외에도 수정, 삭제 등 다른 기능 확인이 필요하시면 위 링크로 접속.

curl --location 'https://api.imgur.com/3/image' \
--header 'Authorization: Client-ID {{clientId}}' \
--form 'image="R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"'

 

첫번째,

이미지를 업로드하기 위해서는 binary, base64, image URL 형식으로 보내야 합니다.

여기서는 formData를 이용하여 binary 파일로 요청합니다.

    const formData = new FormData(); // formData 형식으로 body에 전달
    formData.append('image', file); // formDate에 이미지 파일 추가

 

두번째,

API 요청 형식에 따라 요청합니다.

--header 'Authorization: Client-ID {{clientId}}' \

header에는 위를 반드시 포함하여야 한답니다.

아까 만들어 둔 Client ID 를 포함시켜줍시다.

 

    const res = await fetch('https://api.imgur.com/3/image', {
      method: 'POST',
      headers: {
        Authorization: `Client-ID ${process.env.NEXT_PUBLIC_IMGUR_CLIENT_ID}`,
        Accept: 'application/json',
      },
      body: formData,
    });

body에는 이미지파일이 추가 된 formData를 넣습니다..

{ image: File } 과 같은 형태로 전송이 됩니다.

 

세번째, 요청 결과를 확인합니다.

{
    "data": {
		//...
        "type": "image/jpeg",
        "size": 659939,
        "link": "https://i.imgur.com/6R057H2.jpg"
    },
    "success": true,
    "status": 200
}

요청에 성공하면 위와 같은 형식으로 데이터가 반환이 되는데,
속성 중에 있는 link가 바로 선택한 이미지파일의 주소입니다.

    const result = await res.json(); // imgur 업로드 결과 데이터
    const { link } = result.data; // 이미지 링크

 

이제 생성된 주소를 <image src="주소" /> 와 같은 형태로 Quill의 본문에 삽입하면 되겠죠.


++ 어??? 나는 요청이 안되는데요?

imgur에서 localhost 에서는 요청을 보내도 403, 429 HTTP/2 protocol error 이런 에러만 반환합니다.

 

서버를 직접 만들어 운영하고 계시는 분이라면 서버를 통해서 요청을 보내시면 됩니다. (저는 직접 서버 만들 줄 모름)

그렇지 않다면 저처럼 vercel(AWS나 GCP같은 클라우드 서버)에 배포하여 확인해야 합니다.

 

그런데 애초에 요청이 되는지를 알고 싶다면 POSTMAN으로 확인해봅시다.

워크스페이스를 생성합니다.

 

collection을 만들고,

> POST 요청 선택,

> 요청 주소 입력,

> Headers 탭에서 key에 Authorization 입력,

> value에 Client-ID 입력

 

Body 탭 선택,

> image 키 적고

> FIle 선택,

> 밸류에 파일 등록

그리고 Send 보내면 이렇게 요청이 잘 됐을 경우 데이터를 받아볼 수 있습니다.

오른쪽에 코드도 있으니 저 코드를 참고하여 개성있게 코드 짜면 됩니다.


5. ReactQuill 에 ref 속성 주입하기

// dynamic import. ssr: false 옵션으로 클라이언트에서 동적으로 로드하도록 합니다.
// window와 같은 브라우저 API에 의존할 경우 필요합니다.
const ReactQuill = dynamic(
  async () => {
    const { default: ReactQuillClass } = await import('react-quill');

    const RefExtendedQuill = ({ forwardedRef, ...props }) => <ReactQuillClass ref={forwardedRef} {...props} />;
    return RefExtendedQuill;
  },
  { ssr: false },
);

 

ReactQuill 컴포넌트에는 ref를 직접 작성할 수 없습니다. ref 속성은 없기 때문입니다.

  const quillRef = useRef();
      //...
   <ReactQuill
     ref={quillRef}
    />

 

하지만 직접 dynamic import한 모듈에 ref를 주입한 상태의 컴포넌트를 반환하도록 할 수 있습니다.

react-quill 모듈을 import하여 출력을 해보면 아래와 같이 { default: 컴포넌트 } 형태를 확인할 수 있습니다.

즉, 컴포넌트를 import한 것이고, 컴포넌트에는 ref 속성을 이용할 수 있습니다. 

 

ref 속성 주입 방법은 다음과 같습니다.

forwardedRef 속성을 이용하여 다음과 같은 방법으로 리액트 컴포넌트에 ref 속성을 주입할 수 있습니다.

    // ReactQuillClass 변수에 ReactQuill 컴포넌트 할당
    const { default: ReactQuillClass } = await import('react-quill');

    // 컴포넌트에 ref 속성 주입
    const RefExtendedQuill = ({ forwardedRef, ...props }) => <ReactQuillClass ref={forwardedRef} {...props} />;
    return RefExtendedQuill;

{ default: ReactQuillClass }
=> ReactQuillClass에 default 를 할당합니다.

=> RefExtendedQuill 은 <ReactQuillClass /> 태그를 반환하는 컴포넌트의 변수명입니다.

=> forwardedRef 속성을 <ReactQuillClass /> 태그에 ref={forwardedRef} 와 같은 형태로 전달하면
ref 속성을 이용할 수 있게 됩니다. 

 

다른 이름은 아무렇게 지어도 상관 없지만, forwardedRef 는 정해져 있는 속성입니다. 바꾸면 안됩니다.

 

이제는 useRef()를 이용하여 ReactQuill을 선택할 수 있게 되었습니다.

  const quillRef = useRef();
      // ...
      <ReactQuill
        forwardedRef={quillRef}
      />

6. ReactQuill에 URL 로 이미지 삽입하기

  const onFileUpload = async () => {
    const formData = new FormData(); // formData 형식으로 body에 전달
    formData.append('image', file); // formDate에 이미지 파일 추가

    const res = await fetch('https://api.imgur.com/3/image', {
      method: 'POST',
      headers: {
        Authorization: `Client-ID ${process.env.NEXT_PUBLIC_IMGUR_CLIENT_ID}`,
        Accept: 'application/json',
      },
      body: formData,
    });

    const result = await res.json(); // imgur 업로드 결과 데이터
    const { link } = result.data; // 이미지 링크

    const editor = quillRef.current?.getEditor();
    const range = editor?.getSelection();
    editor.insertEmbed(range.index, 'image', link);
  };

quillRef을 이용하여 quill의 속성을 확인할 수 있게 되었습니다.

 

https://quilljs.com/docs/api/

문서로 확인하려면 위 링크에 접속하면 됩니다.

 

커서의 위치를 확인하는 방법은 다음과 같습니다.

 

직접 접근하는 방법과 editor의 getSelection() 메서드를 이용하는 방법 두가지가 있습니다.

quillRef.current.selection
    const editor = quillRef.current?.getEditor();
    const range = editor?.getSelection();

 

태그를 본문에 삽입하는 방법은 다음과 같습니다.

    const editor = quillRef.current?.getEditor();
    const range = editor?.getSelection();
    editor.insertEmbed(range.index, 'image', link);

insertEmbed() 메서드에 세개의 인자를 넣으면 됩니다.

 

- 이미지를 삽일할 index, (커서 위치)

- type 선택, <image />

- src에 들어갈 이미지 주소 지정

 

공식 레퍼런스 sample

quill.insertEmbed(10, 'image', 'https://quilljs.com/images/cloud.png');

( n번째 인덱스, 'image' type, 이미지 src )


결과

<p>k</p><p><image src="주소" /></p> 와 같은 형태로 이미지 태그가 삽입된 형태로 저장이 됩니다.