μ κ²½νμ΄ λΉμ μ next.js νλ‘μ νΈμ λμμ΄ λκ±°λ μκ°μ μ£Όλ©΄ μ’κ² μ΅λλ€.
κ°μ₯ κ·Όλ³Έμ μΈ μ΄μ λ νΌμμ€ν΄νΈνμ§ μμΌλ©΄ λͺ¨λ DOM λ Έλκ° νκ΄΄λκ³ λ€μ μμ±λλ€λ μ μ λλ€.
- μ΄μ νμ΄μ§μ DOM Nodeκ° λ²λ €μ§κΈ° λλ¬Έμ νμ DOM μνλ₯Ό μμ΄λ²λ¦½λλ€. μλ₯Όλ€μ΄ λ μ΄μμμ ννκ² μμ μ μλ κ²μμ°½ μ λ ₯, λ€λΉκ²μ΄μ λ©λ΄μ ν¬μ»€μ€ μνκ° νμ΄μ§ μ΄λλ§λ€ μ¬λΌμ§λλ€.
- λΉμ°νκ²λ CSS Transitionμ΄ λΆκ°λ₯ν©λλ€. μ λλ©μ΄μ μ΄ νμνλ€λ©΄ μ ν λ€λ₯Έ λ°©λ²μΌλ‘ ꡬνν΄μΌ ν©λλ€.
μ μ΄μ λ‘ μΈν΄ μ ν리μΌμ΄μ μ μ¬μ©μ±κ³Ό μ κ·Όμ±μ΄ λ¨μ΄μ§ μ μμ΅λλ€.
Next.js νλ‘μ νΈλ₯Ό μλμ κ°μ΄ ꡬμ±ν΄λ΄ μλ€.
// pages/index.js
const HomePage = () => (
<>
<header>
<h1>Awesome Title</h1>
</header>
<nav><input placeholder="Search ..." /></nav>
<main>
Home Page, <Link to="/second"><a>to Second Page</a></Link>
</main>
</>
)
// pages/second.js
const SecondPage = () => (
<>
<header>
<h1>Awesome Title</h1>
</header>
<nav><input placeholder="Search ..." /></nav>
<main>
Second Page, <Link to="/home"><a>to Home Page</a></Link>
</main>
</>
)
HomePage
μ SecondPage
λ κ°μ μμ μλ¦¬λ¨ΌνΈ header
, nav
, main
μ κ°μ§μ§λ§ μλ‘ λ€λ₯Έ μ»΄ν¬λνΈμ΄κΈ° λλ¬Έμ λ€λ₯Έ νμ΄μ§λ‘ μλ‘ νμ΄μ§λ₯Ό μ΄λν λ λͺ¨λ μμ μ리먼νΈλ λ²λ €μ§κ³ μλ‘ μμ±λ©λλ€.
next.js 곡μλ¬Έμμ μ ν λλ‘ pages/_app.js
μ λνΌλ₯Ό μΆκ°νλ κ²μΌλ‘ κ°λ₯ν©λλ€.
// pages/_app.js
const App = ({ Component, pageProps }) => (
<MyLayout>
<Component {...pageProps} />
</MyLayout>
);
const MyLayout = ({ children }) => (
<>
<header>
<h1>Puesdo GitHub</h1>
</header>
<nav><input placeholder="Search ..." /></nav>
<main>
{children}
</main>
</>
);
// pages/index.js
const HomePage = () =>
<>Home Page, <Link to="/second"><a>to Second Page</a></Link></>;
// pages/second.js
const SecondPage = () =>
<>Second Page, <Link to="/home"><a>to Home Page</a></Link></>;
μ μ½λμ²λΌ μμ±νλ©΄ λ μ΄μμμ header
, nav
, main
μ리먼νΈκ° νμ μ μ§λ©λλ€.
νμ§λ§ λͺ¨λ νμ΄μ§κ° λμΌν λ μ΄μμ ꡬμ±μ κ°μ‘λ€κ³ κ°μ ν μ μμ΅λλ€.
μλ₯Ό λ€μ΄ μ¬λ¬λΆμ΄ κΉνκ³Ό μ μ¬ν νλ‘μ νΈλ₯Ό κ°λ°νλ€κ³ ν©μλ€.
GitHubμ μ²μ μ μνμ λ 보μ΄λ νμ΄μ§μ Organization νμ΄μ§μ λ μ΄μμμ λ μ¬λ €λ΄
μλ€.
μλ¨ λ€λΉκ²μ΄μ
μ μ μΈνλ©΄ νμ΄μ§μ λλ¨Έμ§ λΆλΆμ μΌμΉνλ λΆλΆμ΄ μμ΅λλ€.
Organization νμ΄μ§λ Repository λͺ©λ‘ μΈμλ Packages, Projects κ·Έλ¦¬κ³ Settings νμ΄ μ‘΄μ¬νκ³ κ° νμ λ μ΄μμμ 곡μ νκ³ κ³ μ νμ΄μ§λ‘ μ‘΄μ¬ν©λλ€.
μ΄ μꡬμ¬νμ λ§μ‘±νκΈ° μν΄ λΉ λ₯΄κ² ꡬνν΄λ΄
μλ€.
// pages/_app.js
const App = ({ Component, pageProps }) => {
const router = useRouter();
const Layout = DefaultLayout;
if (router.pathname.startsWith('/[owner]')) {
Layout = WorkspaceLayout;
}
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
const DefaultLayout = ({ children }) => (
<header>
<h1>Puesdo GitHub</h1>
</header>
<nav>
<Link href="/my-workspace">
<a>My Workspace</a>
</Link>
</nav>
<main>
{children}
</main>
);
const OrganizationLayout = ({ children }) => (
<>
<header>
<h1>Puesdo GitHub</h1>
</header>
<nav>
<a>Repositories</a>
<a>Packages</a>
<a>Settings</a>
</nav>
<main>
{children}
</main>
</>
);
μ μμ λ‘ λ μ΄μμμ λΆλ¦¬ν μ μκ² λ¬μ§λ§ μΈ κ°μ§ λ¬Έμ μ μ΄ μμ΅λλ€.
- μ΄λ€ λ μ΄μμμ΄ λ λλ§λλμ§ νμ΄μ§ μ»΄ν¬λνΈλ§ λ³΄κ³ μ μ μμ΅λλ€.
pages/_app.js
λ₯Ό νμΈν΄μΌ μ μ μμ΅λλ€. colocationμ ν μ μκ³ , νμ΄μ§λ₯Ό κ°λ°νλλ° μμμΌ νλ λ΄μ©μ΄ λ§μμ Έμ μ μ§λ³΄μμ±μ΄ λ¨μ΄μ§λλ€. DefaultLayout
,OrganizationLayout
λ νμ΄μ§μ λ μ΄μμμ΄ μμ μ리먼νΈλ€μ΄ μ¬μ¬μ©λμ§ μμ΅λλ€.- λ μ΄μμ λ¨μμμ data fetchingμ΄ λΆκ°λ₯ν©λλ€.
νμ΄μ§ μ»΄ν¬λνΈμ λ μ΄μμμ λͺ μνλλ‘ λ°κΏλλ€. λ€μκ³Ό κ°μ΄ λ§λ€ μ μμ΅λλ€:
// pages/index.js
const HomePage = () => {...};
HomePage.withLayout = {
render: (children) => (
<>
<header>
<h1>Puesdo GitHub</h1>
</header>
<nav>
<Link href="/my-workspace">
<a>My Workspace</a>
</Link>
</nav>
<main>
{children}
</main>
</>
)
}
// pages/[owner]/index.js
const OrganizationPage = () => {...};
OrganizationPage.withLayout = {
render: (children) => (
<>
<header>
<h1>Puesdo GitHub</h1>
</header>
<nav>
<a>Repositories</a>
<a>Packages</a>
<a>Settings</a>
</nav>
<main>
{children}
</main>
</>
)
}
// pages/_app.js
const App = ({ Component, pageProps, layoutData }) => {
const renderLayout = Component.withLayout?.render
|| (page) => page;
return renderLayout(<Component {...pageProps} />);
};
μ΄λ κ² κ΅¬μ±νλ©΄ λ μ΄μμμ νμ΄μ§μμ λͺ μν μ μμ΅λλ€. κ·Έλ¦¬κ³ λ μ΄μμμ μλ¦¬λ¨ΌνΈ μμΉκ° νΈλ¦¬ λ΄μμ νμ μΌμ νκΈ° λλ¬Έμ DOM λ Έλλ₯Ό μ¬μ¬μ©νκ² λ©λλ€.
λͺλͺ λ μ΄μμμ SSRμ€μ λΉλκΈ°μ μΌλ‘ λ°μ΄ν°λ₯Ό κ°μ ΈμμΌ ν μ μμ΅λλ€.
μμ£Ό κ°λ¨νκ² getInitialProps
μ λΉμ·ν λ
μμ ꡬνν μ μμ΅λλ€.
const App = ({ Component, pageProps, layoutData }) => {
const renderLayout = Component.withLayout?.render
|| (page) => page;
return renderLayout(<Component {...pageProps} />, layoutData);
};
App.getInitialProps = async ({ ctx, Component }) => {
const [pageProps, layoutData] = await Promise.all([
Component.getInitialProps?.(ctx),
Component.withLayout?.fetchInitialData?.(ctx),
]);
return { pageProps, layoutData };
}
7μ€μ μΆκ°νμ¬ μλμ κ°μ΄ μ¬μ©ν μ μμ΅λλ€.
// pages/[owner]/index.js
const OrganizationPage = () => {...};
OrganizationPage.withLayout = {
render: (children, { status, organization }) => status === 'stand-by' ? (
<>
<header>
<h1>{organization.name}</h1>
<p>{organization.description}</p>
</header>
<nav>
<a>Repositories</a>
<a>Packages</a>
<a>Settings</a>
</nav>
<main>
{children}
</main>
</>
) : (
<>
<header>
<h1>404 Not Found</h1>
<p>Requested organization does not exist</p>
</header>
</>
),
fetchInitialData: async (context) => {
const ownerId = context.query.owner;
try {
const organization = await fetchOrganizationByOwnerId(ownerId);
return { organization, status: 'stand-by' };
} catch (error) {
if (error.response.status === 404) {
return { status: 'not-found' };
}
throw error;
}
},
}
μ΄μ λλΆλΆμ λ μ΄μμ μꡬμ¬νμ΄ μΆ©μ‘±λμμ΅λλ€.
- SSR κ³Όμ μ€μ Data Fetching κ°λ₯
- νμ΄μ§ λ¨μλ‘ λ μ΄μμμ λͺ μν μ μμ
- λ μ΄μμ νμ μμκ° μ¬μ¬μ©λ¨
μ΄κ²λ§μΌλ‘λ λλΆλΆμ μν©μμ νλ₯νκ² νμ΄μ§ λ μ΄μμμ ꡬμ±ν μ μμ΅λλ€.
μ¬λ¬ λ μ΄μμμ ν©μ±ν μ μμΌλ©΄ λ μ΄μμμ λΆλ¦¬νκ³ μ¬μ¬μ©νκΈ° ν¨μ¬ μ¬μμ§λλ€. λ€μκ³Ό κ°μ λ μ΄μμλ€μ΄ μλ€κ³ ν΄λ΄ μλ€.
const DefaultLayout = {
render: (children) => (
<>
<TopNavigation />
<div>{children}</div>
</>
),
};
const OrganizationLayout = {
render: (children) => (
<>
<TopNavigation />
<WorkspaceSideNavigation />
{children}
</>
),
};
const OrganizationProjectLayout = {
render: (children) => (
<>
<TopNavigation />
<WorkspaceSideNavigation />
<div>
<ProjectNavigation />
{children}
</div>
</>
),
};
κ° λ μ΄μμμ΄ μ΄μ λ μ΄μμκ³Ό κ΅μ₯ν μ μ¬ν©λλ€.
OrganizationProjectLayout
λ OrganizationLayout
κ° νμ₯λ ννκ³
OrganizationLayout
λ DefaultLayout
κ° νμ₯λ ννμ
λλ€.
μ λ μ΄μμμμ μ€λ³΅λλ λΆλΆμ λΆλ¦¬ν΄μ μ¬μ©ν μ μμΌλ©΄ λμ± νΈλ¦¬ν κ²μ
λλ€.
const DefaultLayout = {
render: (children) => (
<>
<TopNavigation />
{children}
</>
),
};
const OrganizationLayout = {
render: (children) => (
<>
<WorkspaceSideNavigation />
{children}
</>
),
parent: DefaultLayout,
};
const OrganizationProjectLayout = {
render: (children) => (
<div>
<ProjectNavigation />
{children}
</div>
),
parent: OrganizationLayout,
};
볡μ‘ν CSSλ λ λλ§ λ‘μ§μ΄ ν¬ν¨λμ΄ μμ§ μμ μ€μ΄λ λΆλΆμ΄ λΆκ°λμ§ μμ§λ§,
μΌλ°μ μΌλ‘ λ¬Έμ λ₯Ό λΆλ¦¬ν μ μμΌλ©΄ λ¨μνμμΌμ λ μ½κ² κ°λ°ν μ μλ―μ΄ λ μ΄μμλ λΉμ·νκ² μκ°ν μ μμ΅λλ€.
κ° λ μ΄μμλ³λ‘ λ΄λΉνλ λΆλΆμ λ€λ₯΄μ§λ§ μ΅μ’
μ μΌλ‘ OrganizationProjectLayout
κ° μλ μ²λΌ κ²°κ³Όλ₯Ό λ°ννλ©΄ λλκ±°μ£ .
// μ€μ λ‘ μ΄λ κ² λμνμ§ μμ΅λλ€.
// μ°λ¦¬κ° μ μν Layoutλ€μ Componentκ° μλλκΉμ.
<DefaultLayout>
<OrganizationLayout>
<OrganizationProjectLayout>
{children}
</OrganizationProjectLayout>
</OrganizationLayout>
</DefaultLayout>
μ΄λ κ² λλ©΄ λ μ΄μμ λ¨κ³λ³λ‘ κ΄μ¬μ¬λ₯Ό λΆλ¦¬ν μ μκ² λ©λλ€. μμ£Ό κ°λ¨νκ² κ΅¬νν μ μμ΅λλ€.
// pages/_app.js
const App = ({ Component, pageProps, layoutDataList }) => {
const layouts = getLayoutList(Component.withLayout);
return layouts.reduce(
(children, { render }) => render(children, layoutDataList),
<Component {...pageProps}>,
);
};
App.getInitialProps = async ({ ctx, Component }) => {
const [pageProps, layoutDataList] = await Promise.all([
Component.getInitialProps?.(ctx),
// λΉλκΈ° μ²λ¦¬μ μ μνμΈμ
Promise.all(
getLayoutList(Component).map(
layout => layout.withLayout?.fetchInitialData?.(ctx)
)
),
]);
return { pageProps, layoutDataList };
}
const getLayoutList = (layout) => {
const list = [];
let lastLayout = layout;
while (lastLayout) {
list.push(layout);
lastLayout = layout.parent;
}
return list;
}
μ΄ κΈμμ λ€λ£¨μ§ μμ μμ² μ€λ³΅ λ°©μ§λ λ€λ₯Έ μꡬμ¬νλ μμ§λ§, μ κ²½νμ μ κΈ°λ₯λ€λ§ μΆ©μ‘±λλ©΄ λλΆλΆμ λ μ΄μμμ 컀λ²ν μ μμμ΅λλ€.
λ°©λ²μ΄ μλ.. νν.. μλ¬΄νΌ κ°μ¬ν©λλ€ γ γ