์ด์ : ReactDOMServer does not yet support Suspense
์ด๋ ๊ฒ Next.js์์ ์๋ฌ๊ฐ ๋์ค๊ณ ์๋ฌ๊ฐ ๋ฐ์๋ ๊ทผ์์ง๋ฅผ ์๋ ค์ค๋ค๋ฉด, ๋๋ฒ๊น ์ ๊ธฐ๋ฒ์ค ์ญ์ถ์ ์ ์ํ ๋๋ฒ๊น (Debugging by backtracking) ์ผ๋ก ๊ฐ๋ ๊ฒ์ด ํด๊ฒฐํ ํ๋ฅ ์ด ๊ฐ์ฅ ๋์ต๋๋ค.
์คํํ ํ์ด์ง์ ์์ค์ฝ๋๋ ์๋์ ๊ฐ์ต๋๋ค.
function Relay (props: RelayProps) {
const data = useLazyLoadQuery(RelayQuery, {});
console.log(data)
return (
<div>
<h1>Relay</h1>
</div>
);
}
const RelayQuery = graphql`
query relayQuery {
ping
}
`;
export default Relay;
์๋ฌ ๋ฉ์์ง์ ๋ช ์๋ ReactDom Server ๋ง์ด ๋ค์ด๋ดค์ง๋ง, ์ด๋ค ์ผ์ ํ ๊น์?
Server React DOM APIs โ React
The
react-dom/server
APIs let you render React components to HTML on the server. These APIs are only used on the server at the top level of your app to generate the initial HTML. A framework may call them for you. Most of your components donโt need to import or use them. ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๋ฒ ์ธก ๋ ๋๋ง์ ์ํํ๊ธฐ ์ํด "react-dom/server" API๋ฅผ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ด๋ฌํ API๋ ์ฃผ๋ก ์ฑ์ ์ด๊ธฐ HTML์ ์์ฑํ๋ ๋ฐ ํ์ฉ๋๋ค๋ ๋ด์ฉ์ ์ค๋ช ํ๊ณ ์์ต๋๋ค. ๋ํ, ํ๋ ์์ํฌ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ด๋ฌํ API๋ค์ ๋์ ํธ์ถํ ์ ์์ผ๋ฉฐ, ๋๋ถ๋ถ์ React ์ปดํฌ๋ํธ๋ ์ด API๋ค์ ์ง์ ๊ฐ์ ธ์ค๊ฑฐ๋ ์ฌ์ฉํ ํ์๊ฐ ์๋ค.
ReactDomServer๋ ๊ณต์๋ฌธ์์์ ์ค๋ช ํ๋๋ก ์๋ฒ ์ธก ๋ ๋๋ง์ ์ํํ๊ธฐ ์ํด ํ๋ ์์ํฌ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ด๋ฐ API๋ฅผ ํธ์ถํ ์ ์๋ค๊ณ ํฉ๋๋ค.
ReactDOMServer๋ ๋๊ฐ ํธ์ถ ํ๋ ๊ฑธ๊น์?
์๋ฌ๋ฅผ ์ถ์ ํด ๋ณด์์ต๋๋ค.
react-dom ๋ชจ๋์ ReactDOMServerRenderer
์์ ํธ์ถ ํ๊ณ ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ฌ๋ฌ๊ตฐ๋ฐ์์ ํธ๋ฆฌ๊ฑฐ ๋๊ณ ์์ต๋๋ค. ์ด ์๋ฌ๊ฐ ์ ํํ ์ด๋ ๋ถ๋ถ์์ ํธ๋ฆฌ๊ฑฐ๋ฅผ ํ๋์ง ์์ธก ํ ์ ์์ด ์ด๋ ๊ฒ 1111, 2222, 3333 ์ผ๋ก ์ซ์๋ฅผ ๋ฃ์ด๋๊ณ ๋ค์ ์คํ ํ์ต๋๋ค.
๊ทธ๋ฌ๋๋ 1111 ์์ ๊ฐ์ง๊ฐ ๋์๋ค์!
์ ํํ ์์ค์ฝ๋๋ฅผ ์ฐพ์์ต๋๋ค.
์ ๊ฐ ์ดํดํ ๋๋ก ํด์์ ํด๋ณด์๋ฉด this.render()
ํจ์ ํธ์ถ์ try.catch ๋ฌธ์ ์ํด ์๋ฌ๊ฐ ๊ฐ์ง๋์์์ ์ ์ ์์ต๋๋ค. ์ ๊ฐ ๋ง์ฃผํ ์๋ฌ๋ enableSuspenseServerRenderer
๊ฐ false
์ผ ๋ ๊ทธ๋ฆฌ๊ณ !false
์ฆ true
์ผ ๋ ์ด ์๋ฌ๋ฅผ ๋
๋๋ค.
๊ทธ๋ผ ๋จ์๋ enableSuspenseServerRenderer ์ด false
๋ผ์ ์๊ธด ๊ฑฐ๋ผ๊ณ ์ถ์ธกํ ์ ์๊ฒ ๋ค์!
enableSuspenseServerRenderer
์ด ์น๊ตฌ๋ ์ด๋์ ์ด๋ป๊ฒ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๊น์?
์ฐพ์๋ณด๋ ์ด๋์๋ ์ฌ์ฉ๋๋ ๊ณณ์ด ์๊ณ ์ด๋ ๊ฒ default๊ฐ์ผ๋ก false๋ฅผ ๊ฐ์ง ๋ณ์๋ง ์ ์๋์ด์๋ค์.
๊ทธ๋ผ ์ด ํจ์์ ๋ค์ด์ค๋ฉด enableSuspenseServerRenderer
๋ ์ง์ํ์ง ์๋๋ค๋ ๊ฒ์ผ๋ก ์ง์ ํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
๊ทธ๋ผ ์ ์ ์๋ฌ๋ ์ ์ด์ enableSuspenseServerRenderer๋ฅผ ์ง์ํ์ง ์๋ ReactDomSERVER๋ฅผ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํด๋ณธ๋ค๋ฉด, ๊ทผ๋ฐ ์ ๋ Suspense๋ฅผ ๋ช ์์ ์ผ๋ก ์ฌ์ฉํ์ ์ด ์๋๊ฑธ์?
ํ์ฌ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง ํ๊ณ ์๋ ReactDOMServer ๋ ์์ง Suspense ๋ฅผ ์ง์ํ๊ณ ์์ง ์๋ค์ ๋๋ค.
๋ฌธ์ ๋ฅผ ๋ค์ ์ ์ํด๋ณด๊ฒ ์ต๋๋ค.
- ํ์ฌ serverside rendering์ ๋๋ค. ์๋ํ๋ฉด ์๋ฌด๋ฐ next api ์์ด ๊ทธ๋ฅ ํจ์ํธ์ถ ๋ฐฉ์์ผ๋ก ์์ฑํ๊ฒ ๋๋ฉด next.js๋ default๋ก serverside rendering์ผ๋ก ๋์ํฉ๋๋ค.
- ๊ทธ๋ฆฌ๊ณ ์์ค ์ฝ๋์ suspense๋ฅผ ์ฌ์ฉํ์ง ์์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์์ค ์ฝ๋์ Relay๋ก ๋ฐ์ดํฐ๋ฅผ ํ์นญํด์ค๋ useLazyLoadQuery ํจ์๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ Relay๊ณต์๋ฌธ์์์ ์ฐพ์๊ฑด Relay๋ ๋ด๋ถ์ ์ผ๋ก suspense๋ฅผ ์ฌ์ฉํ๋ค ์ ๋๋ค.
tutorial์์๋ Suspense ์ปดํฌ๋ํธ๋ฅผ ์ง์ ์ ๋ http ํต์ ์์๋ response๋ฅผ ๋ฐ์์์ง๋ง ํ๋ฉด์์ ๋ ๋๋ง์ ๋ชปํด์ฃผ๊ณ ์์์ต๋๋ค.
Relay ๋ฅผ ์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง ํ๊ธฐ ์ํด์ Suspense๋ฅผ ์ฌ์ฉํด์ผ ํ๋๋ฐ, Next.js12 ์์๋ suspense๋ฅผ ์ง์ํ์ง ์๋ ๊ฒ ๋ฌธ์ ์์ต๋๋ค.
Suspense๋ฅผ ์ง์ํ์ง ์๋๋ค๋จ? ์ด๋ ๊ฒ next/dynamic ์ผ๋ก suspense์ฌ์ฉ์ด ๊ฐ๋ฅํด์ง๋๋ค.
๊ทธ๋ผ Suspense๋ฅผ ์ฌ์ฉํ๋ฉด ๋๊ฒ ๋ค๊ณ ์๊ฐ ํ ์ ์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ Next.js์ Suspense ๋ ๋ฆฌ์กํธ ๊ธฐ๋ฐ์ Suspense์ด๋ฏ๋ก, ๋ฒ์ ๋ณ๋ก ์ด๋๊น์ง ์ง์ํด์ฃผ๋์ง๋ฅผ ํ์ธํด์ผํฉ๋๋ค.
React17์์๋ Suspense๊ฐ ๊ณต์์ ์ผ๋ก ์ง์๋์ง ์์ง๋ง Next 12๋ฒ์ ์์ React17์ suspense๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ CSR ์ ๋ต์์๋ง ๊ฐ๋ฅํฉ๋๋ค.
์ด ๋ถ๋ถ์ ํด๊ฒฐ ํ๊ธฐ ์ํด SuspenseWapper
์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด csr์์ ์ relay ์ฟผ๋ฆฌ๋ฅผ ์ฒ๋ฆฌํ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋ง ํฉ๋๋ค.
์ฝ๋๋ฅผ ๋ณด๊ฒ ์ต๋๋ค.
import React, { Suspense } from 'react'
interface SuspenseWrapperProps {
fallback: JSX.Element
children: React.ReactNode
}
const isBrowser = typeof window !== 'undefined'
const SuspenseWrapper: React.FunctionComponent<SuspenseWrapperProps> = ({
fallback,
children,
...restProps
}) => {
if (isBrowser) {
return <Suspense fallback={fallback}>{children}</Suspense>
}
return fallback
}
export default SuspenseWrapper
Next.js12 (React17์ ์ฌ์ฉํ ๋) ์์๋ React์์ ์ ๊ณตํ๋ Suspense API ๋ฅผ ์ฌ์ฉํฉ๋๋ค. SuspenseWrapper
๋ fallback
๊ณผย children
์ props๋ก ๋ฐ์ต๋๋ค. ๋ํย ...restProps
๋ฅผ ํตํด ์ถ๊ฐ์ ์ธ props๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
์ด ์ปดํฌ๋ํธ๋ย isBrowser
๋ผ๋ ๋ณ์๋ฅผ ํตํด ํ์ฌ ์ฝ๋๊ฐ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์์ ์คํ๋๋์ง ํ์ธํฉ๋๋ค. ๋ง์ฝ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์ด๋ผ๋ฉด,ย Suspense
ย ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌย children
์ ๋ ๋๋งํ๊ณ , ๋ฐ์ดํฐ ๋ก๋ฉ ์ค์๋ย fallback
์ ํ์ํฉ๋๋ค. ๋ง์ฝ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์ด ์๋๋ผ๋ฉด,ย fallback
๋ง์ ๋ฐํํฉ๋๋ค.
์ด๋ ๊ฒ ์ ์๋ย SuspenseWrapper
ย ์ปดํฌ๋ํธ๋ ๋ค๋ฅธ ํ์ผ์์ ์ํฌํธํ์ฌ ์ฌ์ฉํ ์ ์๋๋ก export ๋ฉ๋๋ค. ์ด ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ฉด, ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง ํ๊ฒฝ์์ Suspense ๊ธฐ๋ฅ์ด ๋ฌธ์ ๋ฅผ ์ผ์ผํค๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
๊ทธ๋ผ ์ ์ ๊ฐ ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธํ์ ๋ ๋ถํฐ ์์ํด์ ๊ฐ ํ์ด์ง ๋ฐ ์ปดํฌ๋ํธ์ ๋ ๋๋ง ์ ๋ต์ ์ด๋ป๊ฒ ๊ฐ์ ธ๊ฐ์ผ ํ ๊น์?
๋ชจ๋ ํ์ด์ง์ ์ปดํฌ๋ํธ๋ค์ CSR ๋ก ๊ฐ์ ธ๊ฐ์ผ ํ ๊น์? ๋ง์ฝ ๋ชจ๋ ํ์ด์ง์ ์ปดํฌ๋ํธ๋ฅผ ํด๋ผ์ด์ธํธ ์ธก ๋ ๋๋ง (CSR)์ผ๋ก ์ฒ๋ฆฌํ๋ค๋ฉด, Next.js๊ฐ ์ ๊ณตํ๋ ์๋ฒ ์ฌ์ด๋ ๋ ๋๋ง (SSR) ๊ธฐ๋ฅ์ ํ์ฉํ์ง ๋ชปํ๊ฒ ๋ฉ๋๋ค. ์ด๋ Next.js์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ค ํ๋๋ฅผ ์ ํํ๋ ๊ฒฐ๊ณผ๊ฐ ๋ฉ๋๋ค. (= ๋ฌผ๋ก ์ ๊ฐ relay๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ๊ธฐ์ Next ํ๋ก์ ํธ์ด๊ธฐ๋ ํ์ง๋ง)
์ ๊ฐ ์ ํํ ๋ฐฉ๋ฒ์,
์ต์ด ํ์ด์ง ๋ ๋๋ง์ SSR์ ์ฌ์ฉํ์ฌ ์ด๊ธฐ ํ์ด์ง ๋ก๋ฉ์ ์ต์ ํํฉ๋๋ค. SSR์ ๊ฒ์ ์์ง ์ต์ ํ (SEO)๋ฅผ ๊ฐ์ ํ๊ณ ์ด๊ธฐ ๋ก๋ฉ ์ฑ๋ฅ์ ํฅ์์ํต๋๋ค. Relay ์ฟผ๋ฆฌ๋ CSR๋ก ์ฒ๋ฆฌํฉ๋๋ค.
import * as React from 'react';
import { useLazyLoadQuery } from 'react-relay';
import { graphql } from 'relay-runtime';
import dynamic from 'next/dynamic';
import SuspenseWrapper from '@app/components/SuspenseWrapper';
export interface RelayProps {
}
const DynamicRelay = dynamic(()=> import('@app/components/SuspenseRelay'),{ssr:false,loading: ()=><p>loading...</p>} )
function Relay (props: RelayProps) {
return (
<>
<h1>Relay Page on SSR </h1>
<SuspenseWrapper fallback={<p>loading...</p>}>
<DynamicRelay/>
</SuspenseWrapper>
</>
);
}
export default Relay
- React์ Relay๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฐ๋จํ ์ปดํฌ๋ํธ๋ฅผ ์ ์ํฉ๋๋ค.
- GraphQL ์ฟผ๋ฆฌ๋ฅผ ์ ์ํฉ๋๋ค. ์ด ์ฟผ๋ฆฌ๋ย
ping
ย ํ๋๋ง ์์ฒญํฉ๋๋ค. ์ด ์ฟผ๋ฆฌ๋ยuseLazyLoadQuery
ย ํ ์ ํตํด ์คํ๋ฉ๋๋ค.
import * as React from 'react';
import { graphql, useLazyLoadQuery } from 'react-relay';
import {SuspenseRelayQuery}from "./../../../__generated__/SuspenseRelayQuery.graphql"
interface SuspenseRelayProps {
}
const RelayQuery = graphql`
query SuspenseRelayQuery{
ping
}
`
const SuspenseRelay: React.FunctionComponent< SuspenseRelayProps> = () => {
const data = useLazyLoadQuery<SuspenseRelayQuery>(RelayQuery,{})
console.log("Data",data)
return <>
<div>
<h1>SuspenseRelay</h1>
</div>
</>;
};
export default SuspenseRelay
SSR์์ ์ด๋ฃจ์ด์ง Suspense์ ๋๋ค.
import React, { Suspense } from 'react'
interface SuspenseWrapperProps {
fallback: JSX.Element
children: React.ReactNode
}
const isBrowser = typeof window !== 'undefined'
const SuspenseWrapper: React.FunctionComponent<SuspenseWrapperProps> = ({
fallback,
children,
...restProps
}) => {
if (isBrowser) {
return <Suspense fallback={fallback}>{children}</Suspense>
}
return fallback
}
export default SuspenseWrapper
์ด๋ก์จ ๋ฒ์ ๊ฐ์ suspense ์ง์ ์ํ๋ฅผ ์๊ฒ ๋์๋๋ ๊ณ๊ธฐ๊ฐ ๋์์ต๋๋ค.