Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[서버 사이드 렌더링 - 2단계] 에프이(박철민) 미션 제출합니다. #68

Merged
merged 10 commits into from
Oct 24, 2024
Next Next commit
fix: 뼈대 코드 수정
chysis committed Oct 14, 2024
commit efe5fd62e76435e14648a6c1e180f4fb4f4cff9e
2 changes: 1 addition & 1 deletion nodemon.json
Original file line number Diff line number Diff line change
@@ -2,5 +2,5 @@
"watch": ["src/server", "src/client"],
"ext": "js jsx json",
"ignore": ["dist", "node_modules"],
"exec": "npm run build:client && npm run build:server && node dist/server/server.js"
"exec": "npm run build:client && npm run build:server && node dist/server.js"
}
127 changes: 23 additions & 104 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -4,9 +4,9 @@
"description": "",
"main": "index.js",
"scripts": {
"build:client": "webpack --config webpack.client.config.js",
"build:client": "rm -rf dist && webpack --config webpack.client.config.js",
"build:server": "webpack --config webpack.server.config.js",
"start": "concurrently \"npm run build:client -- --watch\" \"npm run dev:server\"",
"start": "npm run build:client && npm run build:server && node dist/server.js",
"dev": "nodemon",
"test": "echo \"Error: no test specified\" && exit 1"
},
@@ -25,10 +25,11 @@
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"dotenv": "^16.4.5",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.6.0",
"ignore-loader": "^0.1.2",
"nodemon": "^3.1.7",
"style-loader": "^4.0.0",
"webpack-cli": "^5.1.4"
"webpack-cli": "^5.1.4",
"webpack-node-externals": "^3.0.0"
}
}
6 changes: 3 additions & 3 deletions public/styles/main.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@import "./colors.css";
@import './colors.css';

* {
box-sizing: border-box;
@@ -43,12 +43,12 @@ button.primary {
border-radius: 4px;
}

#wrap {
#root {
min-width: 1440px;
background-color: var(--color-bluegray-100);
}

#wrap h2 {
#root h2 {
font-size: 1.4rem;
font-weight: bold;
margin-bottom: 32px;
3 changes: 2 additions & 1 deletion src/client/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from 'react';
import LogoImage from '@images/logo.png';

const Footer = () => {
return (
<footer className='footer'>
<p>&copy; 우아한테크코스 All Rights Reserved.</p>
<p>
<img src='../assets/images/woowacourse_logo.png' width='180' />
<img src={LogoImage} width='180' />
</p>
</footer>
);
6 changes: 4 additions & 2 deletions src/client/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React from 'react';
import starEmptyImage from '@images/star_empty.png';
import LogoImage from '@images/logo.png';
import { TMDB_BANNER_URL } from '../../server/constants/movies';

const Header = ({ bestMovie }) => {
@@ -12,11 +14,11 @@ const Header = ({ bestMovie }) => {
<div className='overlay' aria-hidden='true'></div>
<div className='top-rated-container'>
<h1 className='logo'>
<img src='../assets/images/logo.png' alt='MovieList' />
<img src={LogoImage} alt='MovieList' />
</h1>
<div className='top-rated-movie'>
<div className='rate'>
<img src='../assets/images/star_empty.png' className='star' />
<img src={starEmptyImage} className='star' />
<span className='rate-value'>{bestMovieRate}</span>
</div>
<div className='title'>{bestMovieTitle}</div>
3 changes: 2 additions & 1 deletion src/client/components/MovieItem.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import starEmptyImage from '@images/star_empty.png';
import { TMDB_THUMBNAIL_URL } from '../../server/constants/movies.js';

const MovieItem = ({ movie }) => {
@@ -11,7 +12,7 @@ const MovieItem = ({ movie }) => {
<img src={thumbnailFullUrl} className='thumbnail' alt={movie.title + '이미지'} />
<div className='item-desc'>
<p className='rate'>
<img src='../assets/images/star_empty.png' className='star' alt='평점' />
<img src={starEmptyImage} className='star' alt='평점' />
<span>{movieRating}</span>
</p>
<strong>{movie.title}</strong>
27 changes: 17 additions & 10 deletions src/server/main.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import "./config.js";
import express from "express";
import path from "path";
import { fileURLToPath } from "url";
import './config.js';
import express from 'express';
import path from 'path';

import movieRouter from "./routes/index.js";
import movieRouter from './routes/index.js';

const app = express();
const PORT = 3000;

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 정적 파일 제공
app.use('/static', express.static(path.join(__dirname)));
// 존재하지 않는 정적 파일에 대한 404 처리
app.use('/static', (req, res) => {
res.status(404).send('Resource not found');
});

app.use("/assets", express.static(path.join(__dirname, "../../public")));
// 메인 페이지 라우트 (React 앱 렌더링)
app.get('/', movieRouter);

app.use("/", movieRouter);
// 그 외 모든 경로에 대한 404 처리
app.use((req, res) => {
res.status(404).send('Page not found');
});

// Start server
// 서버 시작
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
14 changes: 3 additions & 11 deletions src/server/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import { Router } from 'express';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

import React from 'react';
import { renderToString } from 'react-dom/server';
import App from '../../client/App';
import fetchNowPlayingMovies from '../apis/movies.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const router = Router();

router.get('/', async (_, res) => {
router.use('/', async (_, res) => {
const nowPlayingMovies = await fetchNowPlayingMovies();

const templatePath = path.join(__dirname, '../../../views', 'index.html');
const templatePath = path.resolve(__dirname, 'index.html');
const renderedApp = renderToString(<App movies={nowPlayingMovies} />);

const template = fs.readFileSync(templatePath, 'utf-8');
@@ -29,11 +25,7 @@ router.get('/', async (_, res) => {
</script>
`;

const renderedHTML = template
.replace('<!--${INIT_DATA_AREA}-->', initData)
.replace('<!--${MAIN_CONTENT}-->', renderedApp);

res.send(renderedHTML);
res.send(template.replace('<div id="root"></div>', `<div id="root">${renderedApp}</div>${initData}`));
});

export default router;
13 changes: 2 additions & 11 deletions views/index.html
Original file line number Diff line number Diff line change
@@ -3,19 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../assets/styles/reset.css" />
<link rel="stylesheet" href="../assets/styles/main.css" />
<link rel="stylesheet" href="../assets/styles/modal.css" />
<link rel="stylesheet" href="../assets/styles/tab.css" />
<link rel="stylesheet" href="../assets/styles/thumbnail.css" />
<script src="./"></script>
<title>영화 리뷰</title>
<link rel="stylesheet" href="./static/styles/index.css" />
</head>
<body>
<div id="wrap">
<!--${MAIN_CONTENT}-->
</div>
<!--${MODAL_AREA}-->
<div id="root"></div>
</body>
<!--${INIT_DATA_AREA}-->
</html>
22 changes: 16 additions & 6 deletions webpack.client.config.js
Original file line number Diff line number Diff line change
@@ -3,13 +3,13 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
module: 'development',
mode: 'development',
entry: './src/client/main.js',
output: {
path: path.resolve('dist/client'),
path: path.resolve('dist'),
filename: 'bundle.js',
clean: true,
publicPath: '/',
publicPath: '/static/',
},
module: {
rules: [
@@ -28,22 +28,32 @@ module.exports = {
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png)$/,
use: ['file-loader'],
test: /\.(png|jpe?g|gif|svg)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[name][ext]',
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './views/index.html',
filename: 'index.html',
inject: 'body',
}),
new CopyPlugin({
patterns: [
{ from: 'public/images', to: 'images' }, // public 폴더의 이미지를 dist로 복사
{ from: 'public/images', to: 'images' },
{ from: 'public/styles', to: 'styles' },
],
}),
],
resolve: {
alias: {
'@images': path.resolve(__dirname, 'public/images'),
'@styles': path.resolve(__dirname, 'public/styles'),
},
extensions: ['.js', '.jsx'],
},
};
18 changes: 14 additions & 4 deletions webpack.server.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
mode: 'development',
target: 'node',
entry: path.resolve(__dirname, 'src/server/main.js'),
externals: [nodeExternals()],
resolve: {
extensions: ['.js', '.jsx'],
alias: {
'@images': path.resolve(__dirname, 'public/images'),
'@styles': path.resolve(__dirname, 'public/styles'),
},
},
module: {
rules: [
@@ -21,16 +27,20 @@ module.exports = {
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
use: ['ignore-loader'], // CSS 파일을 무시합니다.
},
{
test: /\.(png)$/,
use: ['file-loader'],
test: /\.(png|jpg|jpeg|gif|svg)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[name][ext]',
},
},
],
},
output: {
filename: 'server.js',
path: path.resolve(__dirname, 'dist/server'),
path: path.resolve(__dirname, 'dist'),
publicPath: '/static/',
},
};