참고
server.get('/hashtag/:tag', (req, res) => { // 동적 주소 get 요청 return app.render(req, res, '/hashtag', { tag: req.params.tag }); });
const express = require('express');
const next = require('next');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const dotenv = require('dotenv');
const dev = process.env.NODE_ENV !== 'production';
const prod = process.env.NODE_ENV === 'production';
dotenv.config();
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
server.use(morgan('dev'));
server.use(express.json());
server.use(express.urlencoded({ extended: true }));
server.use(cookieParser(process.env.COOKIE_SECRET));
server.use(
expressSession({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
},
}),
);
server.get('/hashtag/:tag', (req, res) => { // 동적 주소 get 요청
return app.render(req, res, '/hashtag', { tag: req.params.tag });
});
server.get('/user/:id', (req, res) => {
return app.render(req, res, '/user', { id: req.params.id });
});
server.get('*', (req, res) => {
// 모든 get 요청 처리
return handle(req, res);
});
server.listen(3060, () => {
console.log('next+expresss running on port 3060');
});
});
Component.getInitialProps
는 Next가 추가한 React lifecycle의 일종으로서 componentDidMount
보다 먼저 실행됨. 서버에서도 실행되고, 프론트에서도 실행
서버의 데이터를 미리 가져오거나, 서버에서 실행될 동작을 수행할 수 있음.
function NodeBird({ Component, pageProps }) {
return <Component {...pageProps} />
} // Component에서 getInitialProps를 가지고 있다면 return 값을 pageProps에 넣어주어
// 해당 컴포넌트에서 props로 사용할 수 있게 함.
NodeBird.getInitialProps = async context => {
console.log('CONTEXT', context);
const { ctx, Component } = context; // next에서 넣어주는 context
let pageProps = {};
if (Component.getInitialProps) {
// Component (pages 폴더에 있는 컴포넌트)에 getInitialProps가 있다면 return 값을 pageProps에 넣음.
pageProps = await Component.getInitialProps(ctx); // ctx를 컴포넌트에 넘겨준다.
}
return { pageProps };
};
// Only uncomment this getInitialProps if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
export default NodeBird;
http://localhost:3060/hashtag/리엑트
접속 시 콘솔
CONTEXT {
AppTree: [Function: AppTree],
Component: [Function: Hashtag] { getInitialProps: [AsyncFunction] },
router: ServerRouter {
route: '/hashtag',
pathname: '/hashtag',
query: { tag: '리엑트' },
asPath: '/hashtag/%EB%A6%AC%EC%97%91%ED%8A%B8',
isFallback: false
},
ctx: {
err: undefined,
req: IncomingMessage {
_readableState: [ReadableState],
readable: true,
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
socket: [Socket],
connection: [Socket],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Array],
trailers: {},
rawTrailers: [],
aborted: false,
upgrade: false,
url: '/hashtag/%EB%A6%AC%EC%97%91%ED%8A%B8',
method: 'GET',
statusCode: null,
statusMessage: null,
client: [Socket],
_consuming: false,
_dumped: false,
next: [Function: next],
baseUrl: '',
originalUrl: '/hashtag/%EB%A6%AC%EC%97%91%ED%8A%B8',
_parsedUrl: [Url],
params: [Object],
query: {},
res: [ServerResponse],
_startAt: [Array],
_startTime: 2020-03-24T20:31:15.681Z,
_remoteAddress: '::1',
body: {},
secret: 'cookiesecret',
cookies: {},
signedCookies: [Object: null prototype],
_parsedOriginalUrl: [Url],
sessionStore: [MemoryStore],
sessionID: 'lO65UJW_h1vf7FS5Z4Nn_mb38st9kGYc',
session: [Session],
route: [Route],
[Symbol(kCapture)]: false
},
res: ServerResponse {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: true,
useChunkedEncodingByDefault: true,
sendDate: true,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: null,
_hasBody: true,
_trailer: '',
finished: false,
_headerSent: false,
socket: [Socket],
connection: [Socket],
_header: null,
_onPendingData: [Function: bound updateOutgoingData],
_sent100: false,
_expect_continue: false,
req: [IncomingMessage],
locals: [Object: null prototype] {},
_startAt: undefined,
_startTime: undefined,
writeHead: [Function: writeHead],
__onFinished: [Function],
end: [Function: end],
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype]
},
pathname: '/hashtag',
query: { tag: '리엑트' },
asPath: '/hashtag/%EB%A6%AC%EC%97%91%ED%8A%B8',
AppTree: [Function: AppTree],
store: {
dispatch: [Function],
subscribe: [Function: subscribe],
getState: [Function: getState],
replaceReducer: [Function: replaceReducer],
[Symbol(observable)]: [Function: observable]
},
isServer: true
}
}
React로 구현한 Single Page Application은 페이지를 이동할 때 서버에 새로운 요청을 하지 않음으로서 사용자 경험을 증대시킨다.
Next.js
의 Link
컴포넌트는 링크를 클릭하더라도 서버에 새로운 요청을 하지 않을 수 있는 기능을 제공한다.
import Link from 'next/link'
function Home() {
return (
<div>
<Link
href={{ pathname: '/about', query: { name: 'ZEIT' } }} // /about?name=ZEIT
as={`/about/ZEIT`} // /about/ZEIT
>
<a>About us</a>
</Link>
</div>
)
}
export default Home
import React from 'react';
import Link from 'next/link';
const Hashtag = ({ tag }) => {
return (
<Link
href={{ pathname: '/hashtag/', query: { tag: tag } }}
// href만 사용한다면 query string을 사용하게 된다. (/hashtag?tag=리엑트)
as={`/hashtag/${tag}`} // as를 통해 sementic URL 사용이 가능해진다. (/hashtag/리엑트)
>
<a>
{`#${tag}`}
</a>
</Link>
};
Hashtag.getInitialProps = async context => { // context: _app.js의 ctx를 props로 받음
// Compoenet.getInitialProps 가 있으므로 _app.js에서 { pageProps } 를 props로 받음
console.log('Hashtag getInitialProps: ', context.query.tag);
return { tag: context.query.tag }; // _app.js에서 pageProps에 들어가서 Hashtag 컴포넌트에서 사용할 수 있게 함.
};
export default Hashtag;
콘솔 내용: Hashtag getInitialProps: 리엑트