SSR 개념 이해와 Next.js로 실습까지 해보는 SSR 환경 구축하기

Hoon·2019년 12월 5일
104
post-thumbnail

서론

안녕하세요, 영훈입니다. 엄청 오랜만에 글을 썼네요.
이번 글에서는 SSR은 어떤 개념이고 react와 같은 SPA에서 어떻게 SSR 환경을 쉽게 구축하는지에 알아보려고 합니다. 또한 실습을 통해서 Next.js와 좀더 친숙해질 수 있도록 하려고 합니다.

차근차근 따라하면 개념을 쉽게 익힐 수 있게 글을 썼습니다.

SSR은 무엇인가요?

SSR은 Server Side Rendering 약자로 처음 클라이언트가 접속했을때 브라우저에서 자바스크립트 코드를 다운로드 받아 해석 할 때까지 기다리지 않고 서버에서 보여질 HTML을 미리 준비해 클라이언트한테 응답해주는 방식을 서버 사이드 랜더링이라고 합니다. 줄여서 SSR이라고 표현합니다.

SPA에서 SSR을 구현하면 아래와 같은 매커니즘으로 작동합니다.

위에 이미지를 해석해보면 사용자가 웹 페이지에 접속했을 때 서버에서는 사용자에게 랜더링될 HTML 파일을 응답하여 사용자에게 웹 페이지가 바로 랜더링 될 수 있게 해줍니다.

그 후 브라우저는 자바스크립트 파일을 다운로드 받아 해석하고 실행하는 절차를 가지게 됩니다.

이를 통해서 사용자는 브라우저가 자바스크립트 파일을 해석하고 보여질때까지 기다리지 않고 바로 랜더링을 하기 때문에 사용자에게는 좋은 경험을 줄 수 있습니다.

CSR은 무엇인가요?

CSR은 client side rendering 약자로 자바스크립트 파일을 브라우저에서 해석해 랜더링하는 방식입니다.

SPA에서 CSR은 아래와 같은 매커니즘으로 작동합니다.

위에 이미지를 해석해보면 클라이언트가 자바스크립트 파일을 브라우저에서 해석한 후 HTML을 랜더링 해줍니다. 이렇게 되니 SSR과 다르게 CSR은 사용자가 기다려야 하는 시간이 더 많아집니다.

SSR과 CSR 개념 정리

SSR:사용자가 웹 페이지에 접속했을 때 서버가 사용자에게 랜더링될 HTML을 응답하여 브라우저가 바로 랜더링할 수 있게한다. 그 후 CSR과 동일하게 자바스크립트 파일을 다운로드 받고 실행한다.
CSR: 사용자가 웹 페이지에 들어왔을 때 브라우저가 자바스크립트 파일을 다운로드 받아 해석한 후 랜더링한다.

SSR 장점과 단점

SSR 장점
1. 랜더링 속도가 향상됨에 따라서 사용자가 웹 페이지에 접속했을때 바로 볼 수 있음.
2. SEO 최적화가 쉽다.

일반적인 SSR 단점
1. 서버 부하가 비교적 CSR 보다 많음
2. 페이지 이동할때마다 하얀색 화면이 보임

CSR 장점과 단점

CSR 장점
1. 컴포넌트 정의와 재사용을 쉽게 할 수 있다.
2. 사용자에게 부드러운 경험 ( UX ) 를 줄 수 있다. ( SSR은 깜빡거린다. )
3. 페이지 구성에 필요한 데이터만 요청하고 사용해서 서버 부하가 줄어든다.

CSR 단점
1. 초기에 자바스크립트 파일을 받고 실행하는 시간에 딜레이가 있어서 유저가 기다리지 않고 웹 페이지를 이탈 할 수 있다.
2. SEO 최적화가 쉽지 않다 ( 요즘은 크롤링 봇이 좋아져서 CSR에 대응하지 않을까? 생각도 들지만 일단 적어 봅니다. )

SPA에서 SSR을 할 수는 없을까?

SPA에서 SSR을 사용하여 두개의 장점을 얻을 순 없을까? 라는 질문을 할 수 있습니다.

질문에 대답은 서버와 클라이언트가 둘 다 Node.js 사용한다면 가능합니다.

이를 Isomorphic Javascript 라고 합니다.
Isomrhpic Javascript는 서버와 클라이언트에서 동일한 코드가 동작한다는 의미입니다.
즉, React에서 사용되는 코드들이 Node.js 서버 환경에서도 실행할 수 있다는 말입니다.

저는 React가 익숙하니까 React에서 SSR을 할 수 있는 방법들을 소개해 보도록 하겠습니다.

첫 번째는 React가 제공하는 ReactDOMServer 코드를 사용하여 SSR을 구현할 수 있습니다.
Node.js 서버에서 ReactDOMServer를 사용하여 SSR을 구현할 수 있습니다.

하지만 많이 힘들 것 같다는 생각이드네요, ReactDOMServer 사용해서 직접 SSR을 구현하신분이 계신다면 댓글로 경험을 공유해주세요!

두 번째는 Next.js와 같은 React SSR 라이브러리를 사용하여 구현할 수 있습니다.
SPA에서 쉽게 SSR을 할 수 있는 라이브러리들이 있는데요 이 라이브러리를 사용하여 구현하면 비교적 쉽게 SSR을 SPA에서 사용할 수 있습니다.

해당 글에서는 리액트에서 SSR을 쉽게 할 수 있게 하는 라이브러리인 Next.js를 사용하여 SSR을 구현합니다.

Next.js 소개

Next.js로 실습을 진행 하기전 Next.js는 무엇이고 Next.js는 어떤 특징을 가졌는지 정리해 보도록 하겠습니다.

Next.js는 간단하게 말하면 리액트에서 SSR을 쉽게 적용할 수 있게 해주는 라이브러리 입니다.

사용자 시나리오로 알아보는 Next.js 작동 방식

사용자 시나리오를 통해서 next.js가 어떻게 작동하는지 설명해보겠습니다.

첫 번째, 사용자가 처음 페이지를 접속을 요청 했을때 Next.js 서버는 사용자에게 랜더링될 HTML을 응답 값으로 보내줍니다 ( SSR 방식 ).

두 번째, 그 후 브라우저는 추가적인 자바스크립트 번들을 다운로드 받아 실행합니다.

세 번째, 사용자가 해당 페이지에서 다른 페이지로 이동할때는 Next.js에 서버가 아닌 브라우저에서 처리하여 이동하게 합니다. ( CSR 방식 )

Next.js 특징

Next.js는 다음과 같은 특징들을 가졌습니다.

첫 번째, 직관적인 라우팅 시스템 ( dynamic routes 지원, ex: /posts/:id )
두 번째, 페이지를 자동으로 최적화
세 번째, 데이터가 필요한 페이지를 SSR 할 수 있게 지원
네 번째, 빠른 페이지 로드를 위한 자동 코드 스플라이팅
다섯 번째, HMR을 지원
여섯 번째, 쉬운 커스텀마이징을 할 수 있음

이제 글을 통한 이해가 아닌 직접 만들어보면서 next.js 특징을 이해해보도록 하겠습니다.

실습환경 구축하기

nextjs를 사용해 react에서 ssr을 적용하는걸 실습하기 전에 실습을 위해서 미리 사전작업을 해야합니다.

API 설치와 실습에 사용할 Next.js 앱을 설치하도록 하겠습니다.

실습에 사용할 API 서버 설치 및 실행하기

먼저, 실습에 사용할 api 서버를 설치해야합니다.

https://github.com/jeffchoi72/general-server 주소로 가서 아래 git clone 명령어를 통해 소스코드를 다운로드해주세요.

git clone https://github.com/jeffchoi72/general-server.git

그 후 설치가 완료되었다면 해당 디렉터리로 이동하셔서 아래와 같은 명령어를 입력해 주세요

yarn && yarn start

저는 이미 한 번 설치해서 Already up-to-date라고 나오네요.

아래처럼 Server application is up and running on port 4000 이 뜨면 api 서버 실습 환경 구축은 끝났습니다.

실습에 사용할 next.js 앱 설치하기

이제 실습을 진행하면서 수정하고 추가할 next.js 기반에 post-app을 설치해보도록 하겠습니다.

먼저, https://github.com/jeffchoi72/next-posts-app/tree/master 으로 가서 아래 깃 명렁어로 clone을 해주세요.

git clone https://github.com/jeffchoi72/next-posts-app.git

해당 소스코드는 실습이 모두 완성된 코드이므로 실습을 시작할 수 있는 브랜치로 변경하도록 하겠습니다.

해당 프로젝트 디렉토리에서 git checkout next-posts-app-start 명령어를 실행해 주세요

그 후 yarn && yarn next 명령어를 사용해 모듈을 설치하고 실행해 줍니다.

http://localhost:3000/으로 접속했을 때 아래 처럼 Hello world! 가 뜨면 실습 환경 구축이 완료되었습니다.

Next.js 페이지 라우팅 설정 방법

nextjs는 디렉터리와 파일에 라우팅 ( 페이지 주소 ) 의미를 부여하고 있습니다. ( 디렉터리 기반 라우팅 )

Next.js가 어떤 방식으로 디렉터리와 파일에 라우팅 의미를 부여하고 있는지 실습을 통해 알아보도록 하겠습니다.

Nextjs에서는 pages 디렉터리에 존재하는 파일 하나가 페이지 하나라고 의미를 가지고 있습니다.

아래와 같은 pages 디렉터리에 index.tsx는 /주소에 의미를 가지고 있습니다.

pages 디렉터리에 test.tsx 파일을 만들고 컴포넌트를 아래와 같이 만들어 주세요

그 후 http://localhost:3000/test 주소로 웹 페이지에 접속하면 아래와 같은 페이지를 볼 수 있습니다.

그렇다면 다이나믹 라우팅은 어떻게 할까요?

아래와 같이 pages/posts 디렉터리를 만들어준 다음 [id].tsx 이름으로 파일을 만들어주세요

그 후 아래와 같은 내용을 [id].tsx 에 넣어주세요.

그 다음 http://localhost:3000/posts/1 주소를 가면 해당 페이지에 1이 보입니다.

Next.js에 styled-components 적용하기

next.js에 styled-components를 적용해 보도록 하겠습니다.

아래와 같은 명령어로 styled-components를 설치해주세요.

yarn add styled-components && yarn add -D @types/styled-components babel-plugin-styled-components

그 후 프로젝트 최상위 디렉터리에 .babelrc를 만들어주고 다음과 같은 내용을 넣어주세요.

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "styled-components",
      {
        "ssr": true,
        "displayName": true,
        "preprocess": false
      }
    ]
  ]
}

index.tsx 파일에서 아래와 같이 수정해주세요.

http://localhost:3000/ 주소로 웹 페이지에 들어가보면 폰트 색상이 적용되지 않은걸 볼 수 있습니다. ( 만약 적용된걸 보셨다면 새로고침 해주세요 )

그 후 아래와 같이 idnex.tsx와 /posts/[id].tsx를 수정해주세요

index.tsx

import Link from 'next/link';
import styled from 'styled-components';

const Home = () => (
  <div>
    <Title>Hello world!</Title>
    <Link href={`/posts/[id]`} as={`/posts/1`}>
      <a>
        포스트 상세 페이지
      </a>
    </Link>
  </div>
);

export default Home;

const Title = styled.h1`
  color: #2f5fd1;
`;

/posts/[id].tsx

import { NextPage } from 'next';
import Link from 'next/link';
import { useRouter } from 'next/router';

const PostDetailPage: NextPage<{}> = () => {
  const router = useRouter();

  return (
    <div>
      <Link href="/">
      	<a>홈으로</a>
      </Link>
      <br />
      Post ID: {router.query.id}
    </div>
  );
};

export default PostDetailPage;

그 후 홈 -> 포스트 상세 페이지 -> 홈으로 가면 색상이 제대로 적용된걸 볼 수 있습니다.

왜 이런 걸까요? 바로 처음 페이지에 접속했을때 Next.js에서 랜더링될 HTML에 스타일 정보 (styled-components) 를 포함하지 않은채 내려주었기 때문입니다.

그렇다면 홈 -> 포스트 상세 페이지 -> 홈으로 이동했을때 왜 css가 적용되었을까요?

아래 React에서 SSR 플로우 이미지를 보면 유저가 처음 브라우저에 접속했을때 서버에서는 랜더링될 HTML을 준비해 응답합니다. 그 후 브라우저는 자바스크립트 파일을 다운로드받고 실행합니다.

즉, 초기 랜더링은 SSR 방식이고 그 후 CSR 방식을 사용하여 랜더링하기 때문에 styled-components가 적용된 것 입니다.

CSS가 적용안된 이유를 알았고 이제 SSR일때 서버에서 css ( styled-component ) 정보를 포함하여 랜더링하도록 하겠습니다.

_document를 사용해 SSR에서 스타일 정보도 응답하게 하기

_document는 SSR일때 html 및 body 태그에 내용을 추가하거나 수정할때 사용됩니다.

주의

_document 파일에 정의 된 내용들은 SSR일때만 호출됩니다. CSR에서는 호출 되지 않습니다.

아래와 같이 pages/_document.tsx파일을 만들어주고 다음과 같은 내용을 추가해주세요.

import Document, { DocumentContext } from 'next/document';
import { ServerStyleSheet } from 'styled-components';

class CustomDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
        });

      const initialProps = await Document.getInitialProps(ctx);

      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        )
      };
    } catch (error) {
      throw error;
    } finally {
      sheet.seal();
    }
  }
}

export default CustomDocument;

위에 코드를 해석하면 사용자가 처음 페이지를 접속했을때 Next.js에 서버에서 응답할때 styled-components 스타일 정보도 같이 포함하여 랜더링 될 수 있도록해주는 코드입니다.

그 후 다시 웹 페이지에서 새로고침을 해주시면 보일때부터 파란색 글자들이 보일것입니다. ( 안보인다면 next를 종료하고 다시 시작 한 후 새로고침 해주세요 )

Next.js에 핵심 getInitialProps란?

Next.js에서 SSR과 CSR을 어떻게 동시에 사용할 수 있을까요? 정답은 getInitialProps 함수에 있습니다.

getInitialProps 함수에 실행시점은 처음 페이지가 로드 됬을때 서버에서 한번 실행해주고 그 다음 Link Component 혹은 라우팅 API를 사용해 다른 페이지로 이동했을때 getInitialProps를 실행해줍니다. ( 실행되는 환경이 SSR일때는 Next.js 서버이고 CSR일때는 브라우저입니다. )

또한 getInitialProps는 async 함수로 되어있어서 데이터 패칭 같은 비동기 작업들을 쉽게 할 수 있습니다.

예를들어, 페이지에 필요한 데이터들을 가지고 오는 작업들에 유용할거 같습니다.

그렇다면 Next.js에서 API 클라이언트를 사용하여 데이터를 패칭하려면 getInitialProps를 어떻게 사용하는지 실습을 하면서 배워보도록 하겠습니다.

data-fetching 위한 API 연동하기

우선, data-fetching을 하기 위해 api를 연동하도록 하겠습니다.

우선, api 요청을 할 수 있는 http client를 다운로드 받겠습니다. http client는 isomorhpic-unfetch 모듈을 사용하도록 하겠습니다.

isomorphic-unfetch 모듈을 다음과 같은 명령어로 설치해주세요.

yarn add isomorphic-unfetch

해당 프로젝트 최상위로가서 api 디렉터리를 만들어주세요.
그 후 api 디렉터리에 post.api.ts 파일을 만들고 다음과 같은 내용을 추가해줍니다.

import fetch from 'isomorphic-unfetch';

const API_URL = "http://localhost:4000/api";

export interface PostType {
  id: string;
  title: string;
  content: string;
}

export async function getPosts() {
  const request = await fetch(`${API_URL}/posts`);
  const response = await request.json();
  const { posts } = response;

  return {
    posts
  };
}

export async function getPost(postId: string) {
  const request = await fetch(`${API_URL}/posts/${postId}`);
  const response = await request.json();
  const { post } = response;

  return {
    post
  };
}

그 후 api/index.ts 파일을 만들고 아래와 같은 내용을 넣어줍니다.

export * from "./post.api";

API를 연동할 수 있게 되었습니다. 이제 Next.js 실습에 사용할 컴포넌트를 만들어보도록 하겠습니다.

Next.js에 실습에 사용할 컴포넌트 만들기

아래와 같은 웹 페이지를 구성하고 있는 요소들을 컴포넌트로 만들도록 하겠습니다.

최상위 디렉터리에 components를 만들어주세요.

헤더부터 작업하도록 하겠습니다.

components/Header.ts를 정의하고 아래 내용을 넣어줍니다.

참고로, Next.js에서는 react router와 같은 이름으로 되어있는 Link 컴포넌트를 사용해 페이지를 이동합니다. next.js를 사용하여 페이지를 이동할때는 이 컴포넌트를 꼭 사용해야합니다.

import Link from 'next/link';
import React from 'react';
import styled from 'styled-components';

export const Header: React.FC = () => {
  return (
    <Container>
      <Link href="/">
        <Logo>Post-app</Logo>
      </Link>
    </Container>
  );
};

const Container = styled.div`
  backgroudn-color: white;
  color: #222222;
`;

const Logo = styled.a`
  color: #222222;
  font-weight: bold;
  font-size: 24px;
  cursor: pointer;
`;

그 후 components/Post.tsx 컴포넌트를 만들고 아래와 같은 내용을 넣어주세요.

import React from 'react';
import styled from 'styled-components';

import { PostType } from '../api';

interface Props {
  post: PostType;
}

export const Post: React.FC<Props> = ({ post }) => {
  return (
    <Container>
      <Header>
        <Title>{post.title}</Title>
        <Content>{post.content}</Content>
      </Header>
    </Container>
  );
};

const Container = styled.div`
  width: 100%;
`;

const Header = styled.div`
  margin-bottom: 12px;
`;

const Title = styled.h1`
  color: #222222;
  font-size: 18px;
  font-weight: bold;
`;

const Content = styled.p`
  font-size: 14px;
  color: #222222;
`;

components/PostItem.tsx

import Link from 'next/link';
import React from 'react';
import styled from 'styled-components';

import { PostType } from '../api';

interface Props {
  post: PostType;
}

export const PostItem: React.FC<Props> = ({ post }) => {
  return (
    <Link href={`/posts/[id]`} as={`/posts/${post.id}`}>
      <Container>{post.title}</Container>
    </Link>
  );
};

const Container = styled.div`
  width: 100%;
  border: 1px solid #eeeeee;
  padding: 32px;
  cursor: pointer;
`;

components/PostList.tsx

import React from 'react';
import styled from 'styled-components';

import { PostType } from '../api';
import { PostItem } from './PostItem';

interface Props {
  posts: PostType[];
}

export const PostList: React.FC<Props> = ({ posts }) => {
  return (
    <div>
      {posts.map(post => {
        return (
          <PostWrapper key={post.id}>
            <PostItem post={post} />
          </PostWrapper>
        );
      })}
    </div>
  );
};

const PostWrapper = styled.div`
  margin-bottom: 12px;
  :last-child {
    margin-bottom: 0;
  }
`;

페이지에서 사용할 Main Layout 컴포넌트를 만들어줍니다.

components/MainLayout.tsx

import Head from 'next/head';
import React from 'react';
import styled from 'styled-components';

import { Header } from './Header';

interface Props {
  title: string;
}

export const MainLayout: React.FC<Props> = ({ title, children }) => {
  return (
    <Container>
      <Head>
        <title>{title}</title>
        <meta charSet="utf-8" />
        <meta name="viewport" content="initial-scale=1.0, width=device-width" />
      </Head>
      <HeaderWrapper>
        <Header />
      </HeaderWrapper>
      <Main>{children}</Main>
    </Container>
  );
};

const Container = styled.div`
  width: 100%;
  background-color: white;
`;

const HeaderWrapper = styled.header`
  width: 100%;
  padding: 30px 24px;
  border-bottom: 1px solid #eeeeee;
  margin-bottom: 12px;
`;

const Main = styled.main`
  width: 100%;
  padding: 30px 24px;
`;

컴포넌트들을 모두 완성하면 components 디렉터리 파일들은 아래와 같아야합니다.

getInitialProps로 data-fetching 하기

이제 API와 컴포넌트들을 조합하여 페이지를 만들도록 하겠습니다.

pages/index.tsx에 아래와 같은 내용을 넣어주세요.

import { NextPage } from 'next';
import React from 'react';

import { getPosts, PostType } from '../api';
import { MainLayout } from '../components/MainLayout';
import { PostList } from '../components/PostList';

interface Props {
  posts: PostType[];
}

const Home: NextPage<Props> = ({ posts }) => (
  <MainLayout title="">
    <PostList posts={posts} />
  </MainLayout>
);

Home.getInitialProps = async () => {
  const { posts } = await getPosts();

  return {
    posts
  };
};

export default Home;

getInitialProps는 SSR과 CSR에서 실행되는 하이브리드한 next.js에 async 함수입니다.
위에 코드와 같이 주로 데이터를 요청할때 많이 사용됩니다.

그 후 pages/posts/[id].tsx 파일에 아래와 같은 내용을 넣어주세요.

import { NextPage, NextPageContext } from 'next';
import Link from 'next/link';
import React from 'react';
import styled from 'styled-components';

import { getPost, PostType } from '../../api';
import { MainLayout } from '../../components/MainLayout';
import { Post } from '../../components/Post';

interface Props {
  post: PostType;
}

const PostDetailPage: NextPage<Props> = ({ post }) => {
  return (
    <MainLayout title={`${post.title} 게시글`}>
      <Link href="/">
        <BackToHome>홈으로 돌아가기</BackToHome>
      </Link>
      <Divier />
      <Post post={post} />
    </MainLayout>
  );
};

PostDetailPage.getInitialProps = async (ctx: NextPageContext) => {
  const { id } = ctx.query;

  const { post } = await getPost(id as string);

  return {
    post
  };
};

export default PostDetailPage;

const BackToHome = styled.a`
  color: #2f5fd1;
  cursor: pointer;
  display: block;
`;

const Divier = styled.div`
  height: 1px;
  border-bottom: 1px solid #eeeeee;
  margin: 24px 0;
`;

getInitialProps 함수는 인자로 아래와 같은 내용들을 받을 수 있어서 위에와 같이 url 패스를 추출 할 수 있습니다.

pathname - path section of URL
query - query string section of URL parsed as an object
asPath - String of the actual path (including the query) shows in the browser
req - HTTP request object (server only)
res - HTTP response object (server only)
err - Error object if any error is encountered during the rendering

getInitialProps 함수에 실행시점을 확인해 보기 위해 pages/index.tsx와 pages/posts/[id].tsx에 getInitialProps 함수에다가 console.log를 찍어줍니다.

pages/index.tsx

console.log('executed home getInitialProps');

pages/posts/[id].tsx

console.log('exectued posts/[id] getInitialProps');

그 후 http://localhost:3000/ 에서 페이지를 새로고침하면 yarn dev를 실행한 터미널에서 console.log가 찍히는것을 볼 수 있습니다.

그 후 게시글을 아무거나 클릭해서 들어가면 아래와 같이 console.log가 yarn dev를 실행한 터미널이 아닌 브라우저 console창에 찍히는 모습을 확인할 수 있습니다.

getInitialProps 함수는 SSR과 CSR을 구분해 어떤 환경에서 어떻게 처리해야하는지 알아서 구분 해줍니다. Next.js를 사용하면 정말 편하게 SPA에서 SSR을 할 수 있습니다.

마무리

Next.js를 접하면서 공부한걸 정리해서 글을 써보면 좋을 것 같아서 작성했습니다.
Next.js가 어떤 개념이고 어떻게 사용하는지 몰랐었던 분들에게 도움이 되면 좋겠네요.

제가 개념을 잘못 이해해 설명을 잘못했거나 혹은 오탈자가 있다면 댓글로 알려주세요. 감사합니다.

이 글에 목적은 Next.js 이해와 사용하는 방법에 초점을 맞춘 글로써 Next.js에 대한 모든 내용을 담고 있는 글이 아닙니다. 해당 글을 읽고 날개를 달아 Next.js에 대한 지식을 더욱 더 확장해가면 좋겠네요

profile
Software Engineer

11개의 댓글

comment-user-thumbnail
2019년 12월 6일

정석이군요! ㅋㅋ

1개의 답글
comment-user-thumbnail
2019년 12월 11일

좋은 글 감사합니다.

1개의 답글
comment-user-thumbnail
2020년 3월 24일

request to http://localhost:4000/api/posts failed, reason: connect ECONNREFUSED 127.0.0.1:4000
FetchError: request to http://localhost:4000/api/posts failed, reason: connect ECONNREFUSED 127.0.0.1:4000
at ClientRequest. (C:\Users\r7042\OneDrive\바탕 화면\React\general-server\next-posts-app\node_modules\node-fetch\lib\index.js:1455:11)
at ClientRequest.emit (events.js:210:5)
at Socket.socketErrorListener (_http_client.js:406:9)
at Socket.emit (events.js:210:5)
at emitErrorNT (internal/streams/destroy.js:92:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:60:3)
at processTicksAndRejections (internal/process/task_queues.js:80:21)

이 error 지속적으로 발생하는데 혹시 해결방법 아시나요??

1개의 답글
comment-user-thumbnail
2020년 4월 5일

next.js 개념잡는데 큰 도움이 되었습니다!
css에 backgroudn 오타가 하나 있네요!

답글 달기
comment-user-thumbnail
2020년 12월 28일

git에서 clone한게 아니구 개별적으로 create-next-app 생성해서 프로젝트 생성하고 따라하는 도중에 _document.tsx 정의 안했는데도 스타일이 처음 불러올 때 같이 불러와지네요 ..! 무슨 일일까요 ㅠㅠ create-next-app으로 그냥 생성하고 typescript 랑 @type/node랑 styled-components 다 yarn add 하는 중에 _document.tsx를 같이 default로 잡아서 ssr에 style을 불러올 수 있도록 도와주는 무언가가 있는걸까요 ..! 잘 모르곘습니다 ㅠㅠ

1개의 답글
comment-user-thumbnail
2021년 8월 10일

감사합니다

답글 달기
comment-user-thumbnail
2022년 7월 17일

감사합니다.

답글 달기