Next.js에서 AWS S3 첨부파일 다운로드 구현하기 (CORS 에러 없이)

hyeonQyu·2022년 9월 30일
11
post-thumbnail


첨부파일 다운로드 하는 기능을 개발하면서 위와 같이 생긴 UI를 만들게 되었습니다.
AWS S3에 업로드한 파일을 브라우저를 통해 다운로드하는 기능입니다.

HTML 작성

파일 다운로드 버튼을 만들 때는 먼저 아래와 같이 HTML을 작성합니다.

<a href="파일 다운로드 경로" download="다운로드 되는 파일명">UI에 표시되는 파일명</a>

파일 다운로드 경로href에 잘 넣어주면 됩니다.

S3 파일 URL

이를 구현하기 위해서 당연하게도 S3 파일 경로가 필요합니다. 다음과 같이 파일 경로가 있다고 할 때
https://파일경로
처음에는 이 파일 경로 그대로 href에 넣어보았습니다.

<a href="https://파일경로" download="다운로드 되는 파일명">UI에 표시되는 파일명</a>

파일이 다운로드 되길 기대했으나 파일이 새 탭에서 열리더군요.

다운로드 URL 생성하기

href에 들어갈 다운로드 URL을 새로 생성해주어야 합니다. 다운로드 경로는 다음과 같이 생성합니다.

const res = await fetch('https://파일경로');
const blob = await res.blob();
const downloadUrl = window.URL.createObjectURL(blob);

이렇게 얻은 downloadUrlhref에 넣어주면 됩니다.

<a href={downloadUrl} download="다운로드 되는 파일명">UI에 표시되는 파일명</a>

CORS 에러 발생

그러나 웹 브라우저에서 바로 파일경로로 요청을 보냈을 때 CORS 에러가 발생했습니다ㅠㅠ
이를 해결하기 위한 방법으로 S3에서 권한을 설정할 수도 있겠지만 DevOps팀에 요청해야하는 번거로움이 있어서 직접 프론트 코드만 수정하여 해결하고자 했습니다.

어쨌든 이 CORS 에러는 브라우저에서만 발생하기 때문에 요청을 브라우저가 아닌 서버에서 하면 될 것 같았습니다.
우리가 사용하고 있는 Next.js에서는 브라우저가 아닌 서버에서 요청을 보낼 수 있습니다.

서버를 통한 다운로드 URL 요청

먼저 서버 코드를 작성하겠습니다.

기존에 있는 pages > api 폴더 하위에 file.ts 라는 파일을 생성합니다. 이 파일 이름이 곧 브라우저에서 요청을 보낼 주소가 됩니다.

file.ts에 아래와 같이 작성해줍니다.

import { NextApiRequest, NextApiResponse } from 'next';

export interface FileResponse {
    type: string;
    arrayBuffer: number[];
}

export default async function file(req: NextApiRequest, res: NextApiResponse<FileResponse>) {
    const { url } = req.query;
    // 파일 경로에 요청
    const rawData = await fetch(url as string);
    
    const blob = await rawData.blob();
    
    // 브라우저에서 Blob 데이터를 만들기 위한 정보 응답
    res.status(200).send({
        type: blob.type,
        arrayBuffer: Object.values(new Uint8Array(await blob.arrayBuffer())),
    });
}

다음으로는 브라우저에서 방금 작성한 서버로 요청을 보내겠습니다.
이때 우리가 작성한 서버로 요청을 보내게 되면 같은 출처로 요청을 보내기 때문에 CORS 에러가 발생하지 않습니다.

다음은 브라우저단에서 다운로드 URL을 얻어내는 코드입니다.

async function getFile() {
    const fileUrl = 'https://파일경로';
    
    const {
        data: { type, arrayBuffer },
    } = await axios.get('/api/file', { params: { url: fileUrl } });
    
    const blob = await new Blob([Uint8Array.from(arrayBuffer)], { type });
    // <a> 태그의 href 속성값으로 들어갈 다운로드 URL
    const downloadUrl = window.URL.createObjectURL(blob);
}

이제 이 downloadUrl을 href에 넣어주면 됩니다.

<a href={downloadUrl} download="다운로드 되는 파일명">UI에 표시되는 파일명</a>
profile
백엔드가 하고 싶었던 프론트엔드 개발자

3개의 댓글

comment-user-thumbnail
2023년 3월 3일

덕분에 살았습니다ㅠ_ㅠ 감사합니다!!!!!!

1개의 답글
comment-user-thumbnail
2024년 7월 12일

오 좋은 정보 얻었습니다!
잘 써먹었어요~!

답글 달기