본문 바로가기
공부/프로그래밍

[react, springboot] react 와 spring boot 로 구성하기, 묶어 build 하기

by demonic_ 2020. 1. 19.
반응형

Springboot 프로젝트를 생성한다.

여기서는 Intellij 를 이용해 생성했고, Gradle과 Java 버전 11을 사용했다.

이 과정은 생략하겠다.

react를 설치하는 방법은 다양하지만 여기서는 Create react app 을 이용해 생성하고자 한다

관련 항목은 아래 링크에서 설명되어 있다

https://reactjs-kr.firebaseapp.com/docs/installation.html

 

Getting Started – React

A JavaScript library for building user interfaces

reactjs.org

 

node.js 6버전 이상이 설치되어 있어야 한다

npm을 이용해 create-react-app 을 설치한다

npm install -g create-react-app

 

frontend 폴더를 만들고 그 안에 리액트 프로젝트를 생성한다

mkdir frontend
cd frontend

create-react-app my-app

 

다음과 같은 로그가 나오고 완료된다.

Success! Created my-app at /Users/dgpark/git/project/react/frontend/my-app
Inside that directory, you can run several commands:

  npm start
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd my-app
  npm start

Happy hacking!
​

 

폴더에 접근한 뒤 리액트를 실행한다.

cd my-app
npm start


> my-app@0.1.0 start /react/frontend/my-app
> react-scripts start

ℹ 「wds」: Project is running at http://192.168.2.139/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/dgpark/git/project/react/frontend/my-app/public
Compiled successfully!

You can now view my-app in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://192.168.2.139:3000/

Note that the development build is not optimized.
To create a production build, use npm run build.
​

 

localhost:3000 으로 접속하면 다음 화면이 뜬다.

 

폴더구성은 다음과 같다

 

 

 

# React 를 작성한다

편의를 위해 react-bootstrap와 react-router-dom을 설치, 사용한다

그리고 API 호출을 위해 axios 도 설치한다

npm install bootstrap react-bootstrap --save
npm install react-router-dom --save
npm install axios --save

 

2개 메뉴를 생성(Main, Dashboard)

MainComponent.jsx

import React, {Component} from "react";

class MainComponent extends Component {    
    render() {
        return(
            <div>
                Main 페이지
            </div>
        )
    }
}

export default MainComponent

 

Dashboard.jsx

import React, {Component} from "react";


class DashboardComponent extends Component {
    render() {
        return(
            <div>
                Dashboard 페이지
            </div>
        )
    }
}

export default DashboardComponent

 

router 를 이용해 페이지를 연결한다

TopMenuComponent.jsx

import React, {Component} from "react";
import {Navbar} from "react-bootstrap";
import {BrowserRouter as Router, Route} from "react-router-dom";

import MainComponent from './MainComponent'
import DashboardComponent from './DashboardComponent'

class TopMenuComponent extends Component {
    render() {
        return (
            <Router>
                <Navbar
                    bg="dark"
                    variant="dark"
                    className="mb-4" >
                    <Navbar.Brand href="/">
                        Home
                    </Navbar.Brand>
                    <Navbar.Brand href="/main">
                        Main
                    </Navbar.Brand>
                    <Navbar.Brand href="/dashboard">
                        Dashboard
                    </Navbar.Brand>
                </Navbar>

                <Route path="/main" component={MainComponent} />
                <Route path="/dashboard" component={DashboardComponent} />
            </Router>
        )
    }
}

export default TopMenuComponent;

 

설정된 것을 App.js 에 넣어 마무리한다

import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css'
import './App.css';

import TopMenuComponent from "./component/TopMenuComponent";

function App() {
  return (
      <div className="App">
          <div>
              <TopMenuComponent>
              </TopMenuComponent>
          </div>
      </div>
  );
}

export default App;

 

react 서버를 새로 실행한다

npm start

그럼 이제 axios를 이용해 호출해보자

 

 

 

# 서버와 통신하기 (API)

ApiController 를 생성하여 매핑한다

@RestController
public class ApiController {

    @GetMapping("/api/hello")
    public HashMap hello() {
        HashMap result = new HashMap();
        result.put("message", "안녕하세요");

        return result;
    }
}

 

MainComponent.jsx 파일을 열어 통신코드를 추가한다.

import React, {Component} from "react";
import axios from "axios";

class MainComponent extends Component {    
    constructor(props) {
        super(props)
        this.state = {
            message: ""
        }
    }

    componentDidMount() {
        this.getApi();
    }

    getApi = () => {
        axios.get("http://localhost:8080/api/hello")
            .then(res => {
                console.log(res);
                this.setState({
                    message: res.data.message
                })
            })
            .catch(res => console.log(res))
    }

    render() {
        return(
            <div>
                Main 페이지
            </div>
        )
    }
}

export default MainComponent

 

브라우저에서 확인하면 브라우저 로그에 다음 에러를 확인할 수 있다

Access to XMLHttpRequest at 'http://localhost:8080/api/hello' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
MainComponent.jsx:11 Error: Network Error
    at createError (createError.js:16)
    at XMLHttpRequest.handleError (xhr.js:83)

 

 

CORS는 Cross Origin Resource Sharing의 약자로 도메인 또는 포트가 다른 서버의 자원을 요청하는 매커니즘이다

상세한것은 아래 페이지 참조한다

 

https://velog.io/@wlsdud2194/cors

 

CORS에 대한 간단한 고찰

이 포스트에서는 CORS에 대한 이슈에 대해서 다뤄볼려고 합니다. 웹 개발을 하다보면 한번쯤 겪게되는 이슈로 클라이언트와 서버의 오리진이 다를 때 발생하는 이슈입니다. 🤔 CORS? 크로스 도메인? CORS는 Cross Origin Resource Sharing의 약자로 도메인 또는 포트가 다른 서버의 자원을 요청하는 매커니즘을 말합니다. 이...

velog.io

 

이것을 해제하기 위해선 서버측에 작업이 필요하다

WebConfig.java 를 생성하여 다음과 같이 설정한다

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
                .addMapping("/api/**")
                .allowedOrigins("http://localhost:3000")
        ;
    }
}

 

 

서버를 새로 실행한 후 새로고침해보면 다음처럼 데이터를 받아오는데 성공한다

{data: {…}, status: 200, statusText: "", headers: {…}, config: {…}, …}
data: {message: "안녕하세요"}
status: 200
statusText: ""
headers: {content-type: "application/json"}
config: {url: "http://localhost:8080/api/hello", method: "get", headers: {…}, transformRequest: Array(1), transformResponse: Array(1), …}
request: XMLHttpRequest {readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, onreadystatechange: ƒ, …}
__proto__: Object

 

 

 

 

# frontend 와 backend 같이 build 하기

frontend 빌딩하기

# 터미널로 frontend/my-app 폴더로 접근한 후
npm run build

 

 

실행하고 나면 아래와같이 폴더가 생성된다

 

다만 이대로 bootJar 로 만들면 frontend 의 build 내용이 포함되지 않는다

그래서 gradle 설정을 변경해야 한다

build.gradle 파일은 열어 다음을 수정하면 npm run build 와 자바 컴파일을 동시에 한다

plugins {
    ...

    # moowork.node 추가
    id "com.moowork.node" version "1.3.1"
}

apply plugin: "com.moowork.node"
...

# (아래 추가)

def webappDir = "$projectDir/frontend/my-app"

task appNpmInstall(type: NpmTask) {
    workingDir = file("${webappDir}")
    args = ["run", "build"]
}

task copyWebApp(type: Copy) {
    from 'frontend/my-app/build'
    into "build/resources/main/static"
}

copyWebApp.dependsOn(appNpmInstall)
compileJava.dependsOn(copyWebApp)

 

이제 bootJar 를 생성하면 build/resources/main/static 에 파일이 생성되 있음을 확인할 수 있다.

 

build/libs 폴더에 있는 jar 파일을 실행하면 localhost:8080 으로도 react가 연결됨을 확인할 수 있다.

 

 

문제는 Main, Dashboard 의 연결이 안된다는 점이다.

다음과 같이 404에러가 발생한다. Spring 에서 해당 URL Mapping 을 찾을 수 없기 때문이다

 

서버쪽에서 WebController를 생성하여 error가 발생해도 index.html 로 이동하도록 유도한다

 

WebController.java

 

@Controller
public class WebController implements ErrorController {
    @GetMapping({"/", "/error"})
    public String index() {
        return "index.html";
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }
}

 

이제 다시 bootJar 를 실행하고 만들어진 jar 파일을 실행해보자

java -jar build/libs/react-0.0.1-SNAPSHOT.jar

 

제대로 이동되는 것을 확인할 수 있다.

 

 

 

 

github:

https://github.com/lemontia/springboot_react

 

lemontia/springboot_react

Contribute to lemontia/springboot_react development by creating an account on GitHub.

github.com

 

반응형

댓글21

  • 배효성 2020.02.27 10:18

    안녕하세요 소스 좀 보려고 하는데 깃헙 주소에서 frontend 폴더 밑에 my-app에 접근할수가 없네요
    그래서 mainComponent.jsx Dashboard.jsx 등 파일의 위치? 를 어디에 해야 될지 잘 모르겠어요
    답글

  • 익명 2020.05.11 13:37

    비밀댓글입니다
    답글

  • 부끄럼쟁이 2020.06.02 13:31

    spring react 함께 빌드할때
    error 발생시 index.html 로 가게 하는건 되는데
    제대로 컨트롤러에 매핑 되었을때 index.html로 가는건 왜 에러가 발생할까요? 궁금합니다.
    답글

    • BlogIcon demonic_ 2020.06.29 14:37 신고

      음...저도 궁금하네요. 혹시 추가로 만든 컨트롤러가 없는데도 그리 이동하나요?
      디버깅을 해보면 좋을거 같습니다.

  • prev 2020.06.28 15:27

    너무 감사합니다!!!
    답글

  • 중고신입 2020.07.24 17:32

    js랑 jsx랑 섞어서 사용했는데 이유가 있으신가요?
    답글

  • BlogIcon D36choi 2020.08.10 14:09 신고

    리액트와 스프링부트를 이용해서 프로젝트 하려하는데... 큰 도움이 되었습니다 감사합니다!
    답글

  • BlogIcon 이숭간 2021.03.21 00:39 신고

    좋은글 감사합니다!!
    답글

  • 덕왕 2021.03.26 13:29

    블로그 글과 git 소스에 도움 많이 받았습니다. 한가지 궁금한 게 있는데요, 이대로 진행할 때 git에 commit이랑 push하게 되면 이상하게 frontend/my-app 부분만 올라가는데요 혹시 설정을 변경해야 할 게 있을까요? 수시간 삽질끝에 답이 안나와서 문의드려요.
    답글

  • 덕왕 2021.03.27 16:57

    넵 맞아요. 백엔드가 안 올라가네요.. 아예 처음부터 스프링부트 프로젝트 띄우고 깃 쉐어부터 한 다음에 알려주신 방법으로 해 볼까 하는데 잘 되려나 모르겠어요
    답글

  • BlogIcon e-pd 2021.03.29 17:14 신고

    local에서는 잘 빌드가 되는데, jenkins나 github action같은 ci, cd에서는
    Execution failed for task ':appNpmInstall'.
    에러가 나는데요. node_module이 없어서 그런것 같네요.

    배포할때는 어떻게 설정하셨는지 혹시 알 수 있을까요?
    답글

    • BlogIcon demonic_ 2021.03.30 16:36 신고

      음 저는 자동배포까진 연결을 안했어요.. 요즘 플젝은 프앤 백앤 분리해서 각각 올리는중이라... 프앤 올리는 곳은 스크립트 심어놨습니다.

  • 익명 2021.04.02 13:56

    비밀댓글입니다
    답글

    • BlogIcon demonic_ 2021.04.02 15:09 신고

      음 다운받아서 실행해보려하니 여러 설정때매 안되는거 같아 대충 파일만 봣는데요.
      시큐리티에 SecurityConfig 이 파일을 전체주석하고 한번 해보세요. 말씀처럼 시큐리티 적용이 된거 같은데, 해보시고 된다면 해당 파일의 옵션에 로그인 안해도 되는 것을 찾아 하심 될거 같습니다. 대단하십니다!