Nx로 Next.js 애플리케이션 최적화하기
게시 됨: 2022-03-10이 기사에서는 Nx와 풍부한 기능을 사용하여 고성능 Next.js 애플리케이션을 최적화하고 구축하는 방법을 살펴보겠습니다. Nx 서버를 설정하는 방법, 기존 서버에 플러그인을 추가하는 방법, 실용적인 시각화를 통한 모노레포 개념에 대해 알아보겠습니다.
애플리케이션을 최적화하고 애플리케이션 전체에서 재사용 가능한 구성 요소를 효과적으로 생성하려는 개발자라면 이 기사에서 애플리케이션을 빠르게 확장하는 방법과 Nx로 작업하는 방법을 보여줍니다. 따라 하려면 Next.js 프레임워크와 TypeScript에 대한 기본 지식이 필요합니다.
Nx 란 무엇입니까?
Nx는 최신 기술 및 라이브러리와 원활하게 통합하는 동시에 강력한 명령줄 인터페이스(CLI), 캐싱 및 종속성 관리를 제공하여 모든 규모에서 설계, 테스트 및 구축하는 데 도움이 되는 오픈 소스 빌드 프레임워크입니다. Nx는 개발자에게 최신 프레임워크, 테스트 및 도구를 위한 고급 CLI 도구 및 플러그인을 제공합니다.
이 기사에서는 Nx가 Next.js 애플리케이션과 어떻게 작동하는지에 초점을 맞출 것입니다. Nx는 Cypress, Storybook 및 styled-components와 같은 Next.js 애플리케이션에서 테스트 및 스타일 지정을 위한 표준 도구를 제공합니다. Nx는 여러 애플리케이션의 소스 코드와 라이브러리를 보유할 수 있는 작업 공간을 생성하여 애플리케이션에 대한 모노리포지토리를 용이하게 하여 애플리케이션 간에 리소스를 공유할 수 있도록 합니다.
Nx를 사용하는 이유
Nx는 개발자에게 애플리케이션의 종단 간(E2E) 테스트를 위한 상용구, 스타일 라이브러리 및 모노레포를 포함하여 즉시 사용 가능한 합리적인 양의 기능을 제공합니다.
Nx를 사용하면 많은 이점이 있으며 이 섹션에서는 그 중 몇 가지를 살펴보겠습니다.
- 그래프 기반 작업 실행
Nx는 분산 그래프 기반 작업 실행 및 계산 캐싱을 사용하여 작업 속도를 높입니다. 시스템은 그래프 시스템을 사용하여 작업 및 명령을 예약하여 각 작업을 실행해야 하는 노드(즉, 응용 프로그램)를 결정합니다. 이는 애플리케이션 실행을 처리하고 실행 시간을 효율적으로 최적화합니다. - 테스트
Nx는 단위 테스트 및 E2E 테스트를 위해 사전 구성된 테스트 도구를 제공합니다. - 캐싱
Nx는 캐시된 프로젝트 그래프도 저장합니다. 이렇게 하면 업데이트된 파일만 재분석할 수 있습니다. Nx는 마지막 커밋 이후 변경된 파일을 추적하고 해당 파일에 대해서만 테스트, 빌드 및 작업을 수행할 수 있도록 합니다. 이렇게 하면 대규모 코드 기반으로 작업할 때 적절한 최적화가 가능합니다. - 종속성 그래프
시각적 종속성 그래프를 사용하면 구성 요소가 서로 상호 작용하는 방식을 검사할 수 있습니다. - 클라우드 스토리지
Nx는 또한 클라우드 스토리지 및 GitHub 통합을 제공하므로 팀 구성원과 링크를 공유하여 프로젝트 로그를 검토할 수 있습니다. - 코드 공유
모든 프로젝트에 대해 새로운 공유 라이브러리를 만드는 것은 꽤 부담스러울 수 있습니다. Nx는 이러한 복잡성을 제거하여 앱의 핵심 기능에 집중할 수 있도록 합니다. Nx를 사용하면 애플리케이션 간에 라이브러리와 구성 요소를 공유할 수 있습니다. 프론트엔드와 백엔드 애플리케이션 간에 재사용 가능한 코드를 공유할 수도 있습니다. - 모노리포지토리 지원
Nx는 여러 애플리케이션을 위한 하나의 작업 공간을 제공합니다. 이 설정을 사용하면 하나의 GitHub 리포지토리에 작업 공간 아래의 다양한 애플리케이션에 대한 코드 소스를 저장할 수 있습니다.
퍼블리싱 가능한 라이브러리를 위한 Nx
Nx를 사용하면 게시 가능한 라이브러리를 만들 수 있습니다. 이것은 모노리포지토리 외부에서 사용할 라이브러리가 있는 경우 필수적입니다. Nx Storybook 통합으로 조직 UI 구성 요소를 개발하는 경우 Nx는 스토리와 함께 게시 가능한 구성 요소를 생성합니다. 게시 가능한 구성 요소는 이러한 구성 요소를 컴파일하여 외부 레지스트리에 배포할 수 있는 라이브러리 번들을 생성할 수 있습니다. monorepo에서만 사용되는 라이브러리를 생성하는 데 사용되는 --buildable
과 달리 라이브러리를 생성할 때 --publishable
옵션을 사용합니다. Nx는 게시 가능한 라이브러리를 자동으로 배포하지 않습니다. nx build mylib
(여기서 mylib
는 라이브러리 이름)와 같은 명령을 통해 빌드를 호출할 수 있습니다. 그러면 외부 레지스트리에 배포할 수 있는 최적화된 번들을 dist
/ mylib
폴더에 생성합니다.
Nx는 Next.js를 사전 설정으로 사용하여 새 작업 공간을 만들거나 기존 작업 공간에 Next.js를 추가하는 옵션을 제공합니다.
Next.js를 사전 설정으로 사용하여 새 작업 공간을 만들려면 다음 명령을 사용할 수 있습니다.
npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo
이 명령은 "todo"라는 이름의 Next.js 앱과 styled-components
를 스타일 라이브러리로 사용하여 새 Nx 작업 공간을 만듭니다.
그런 다음 다음 명령을 사용하여 Next.js 애플리케이션을 기존 Nx 작업 공간에 추가할 수 있습니다.
npx nx g @nrwl/next:app
Next.js 및 Nx 애플리케이션 빌드
Next.js용 Nx 플러그인에는 Next.js 애플리케이션을 실행하고 최적화하기 위한 도구와 실행기가 포함되어 있습니다. 시작하려면 next
을 사전 설정으로 사용하여 새 Nx 작업 공간을 만들어야 합니다.
npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo
위의 코드 블록은 새로운 Nx 작업 공간과 Next.js 애플리케이션을 생성합니다. Nx Cloud를 사용하라는 메시지가 표시됩니다. 이 자습서에서는 "아니요"를 선택한 다음 종속성이 설치될 때까지 기다립니다. 완료되면 다음과 유사한 파일 트리가 있어야 합니다.
happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-e2e ┃ ┗ .gitkeep ┣ libs ┣ node_modules ┣ tools ┣ .editorconfig ┣ .eslintrc.json ┣ .gitignore ┣ .prettierignore ┣ .prettierrc ┣ README.md ┣ babel.config.json ┣ jest.config.js ┣ jest.preset.js ┣ nx.json ┣ package-lock.json ┣ package.json ┣ tsconfig.base.json ┗ workspace.json
apps
폴더에는 할 일 앱에 대해 사전 구성된 E2E 테스트와 함께 Next.js 애플리케이션 "todo"가 있습니다. 이 모든 것은 강력한 Nx CLI 도구로 자동 생성됩니다.
앱을 실행하려면 npx nx serve todo
명령을 사용하세요. 앱 제공이 완료되면 아래 화면이 표시되어야 합니다.
API 빌드
이 시점에서 작업 공간을 설정했습니다. 다음은 Next.js 애플리케이션에서 사용할 CRUD API를 구축하는 것입니다. 이를 위해 Express를 사용할 것입니다. monorepo 지원을 보여주기 위해 작업 공간에서 서버를 애플리케이션으로 구축할 것입니다. 먼저 다음 명령을 실행하여 Nx용 Express 플러그인을 설치해야 합니다.
npm install --save-dev @nrwl/express
완료되면 제공된 작업 공간에서 Express 앱을 설정할 준비가 되었습니다. Express 앱을 생성하려면 아래 명령을 실행하세요.
npx nx g @nrwl/express:application --name=todo-api --frontendProject=todo
nx g @nrwl/express:application
명령은 추가 사양 매개변수를 전달할 수 있는 Express 애플리케이션을 생성합니다. 애플리케이션의 이름을 지정하려면 --name
플래그를 사용하십시오. Express 앱을 사용할 프런트 엔드 애플리케이션을 나타내려면 작업 공간의 앱 이름을 --frontendProject
에 전달합니다. Express 앱에는 몇 가지 다른 옵션을 사용할 수 있습니다. 이 작업이 완료되면 apps
폴더에 todo-api
폴더가 추가된 업데이트된 파일 구조를 갖게 됩니다.
happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-api ┃ ┣ todo-e2e ┃ ┗ .gitkeep …
todo-api
폴더는 main.ts
항목 파일이 있는 Express 상용구입니다.
/** * This is not a production server yet! * This is only minimal back end to get started. */ import * as express from 'express'; import {v4 as uuidV4} from 'uuid'; const app = express(); app.use(express.json()); // used instead of body-parser app.get('/api', (req, res) => { res.send({ message: 'Welcome to todo-api!' }); }); const port = process.env.port || 3333; const server = app.listen(port, () => { console.log(`Listening at http://localhost:${port}/api`); }); server.on('error', console.error);
우리는 이 앱 내에서 경로를 생성할 것입니다. 시작하기 위해 앱 선언 바로 아래에 두 개의 키-값 쌍, item
및 id
를 사용하여 객체 배열을 초기화합니다.
/** * This is not a production server yet! * This is only minimal back end to get started. */ import * as express from 'express'; import {v4 as uuidV4} from 'uuid'; const app = express(); app.use(express.json()); // used instead of body-parser let todoArray: Array<{ item: string; id: string }> = [ { item: 'default todo', id: uuidV4() }, ]; …
다음으로, 우리는 app.get()
아래의 모든 할일 목록을 가져오기 위한 경로를 설정할 것입니다:
… app.get('/api', (req, res) => { res.status(200).json({ data: todoArray, }); }); …
위의 코드 블록은 현재 todoArray
값을 반환합니다. 결과적으로 배열에서 할 일 항목을 생성, 업데이트 및 제거하는 경로가 있습니다.
… app.post('/api', (req, res) => { const item: string = req.body.item; // Increment ID of item based on the ID of the last item in the array. let id: string = uuidV4(); // Add the new object to the array todoArray.push({ item, id }); res.status(200).json({ message: 'item added successfully', }); }); app.patch('/api', (req, res) => { // Value of the updated item const updatedItem: string = req.body.updatedItem; // ID of the position to update const id: string = req.body.id; // Find index of the ID const arrayIndex = todoArray.findIndex((obj) => obj.id === id); // Update item that matches the index todoArray[arrayIndex].item = updatedItem res.status(200).json({ message: 'item updated successfully', }); }); app.delete('/api', (req, res) => { // ID of the position to remove const id: string = req.body.id; // Update array and remove the object that matches the ID todoArray = todoArray.filter((val) => val.id !== id); res.status(200).json({ message: 'item removed successfully', }); }); …
새 할일 항목을 생성하려면 새 항목의 값이 문자열이면 됩니다. 서버의 배열에 있는 마지막 요소의 ID를 증가시켜 ID를 생성합니다. 기존 항목을 업데이트하려면 항목의 새 값과 업데이트할 항목 개체의 ID를 전달합니다. 서버에서 forEach
메소드를 사용하여 각 항목을 반복하고 ID가 요청과 함께 전송된 ID와 일치하는 위치에서 항목을 업데이트합니다. 마지막으로 배열에서 항목을 제거하기 위해 요청과 함께 제거할 항목의 ID를 보냅니다. 그런 다음 배열을 필터링하고 요청과 함께 보낸 ID와 일치하지 않는 모든 항목의 새 배열을 반환하고 새 배열을 todoArray
변수에 할당합니다.
참고: Next.js 애플리케이션 폴더를 보면 아래 구성의 proxy.conf.json
파일이 표시되어야 합니다.
{ "/api": { "target": "http://localhost:3333", "secure": false } }
이렇게 하면 프록시가 생성되어 /api
와 일치하는 경로에 대한 모든 API 호출이 todo-api
서버를 대상으로 하도록 허용합니다.
Nx로 Next.js 페이지 생성하기
Next.js 애플리케이션에서 새로운 페이지인 home
과 item 컴포넌트를 생성할 것입니다. Nx는 페이지를 쉽게 생성할 수 있는 CLI 도구를 제공합니다.
npx nx g @nrwl/next:page home
이 명령을 실행하면 페이지에 사용할 스타일 라이브러리를 선택하라는 메시지가 표시됩니다. 이 기사에서는 styled-components
를 선택합니다. 짜잔! 우리 페이지가 만들어졌습니다. 구성 요소를 만들려면 npx nx g @nrwl/next:component todo-item
을 실행하십시오. 이것은 todo-item
컴포넌트가 있는 component
폴더를 생성할 것입니다.
Next.js 애플리케이션의 API 소비
각 할 일 항목에는 할 일 항목을 편집하고 삭제하는 두 개의 버튼이 있습니다. 이러한 작업을 수행하는 비동기 함수는 홈 페이지에서 소품으로 전달됩니다.
… export interface TodoItemProps { updateItem(id: string, updatedItem: string): Promise<void>; deleteItem(id: string): Promise<void>; fetchItems(): Promise<any>; item: string; id: string; } export const FlexWrapper = styled.div` width: 100%; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #ccc; padding-bottom: 10px; margin-top: 20px; @media all and (max-width: 470px) { flex-direction: column; input { width: 100%; } button { width: 100%; } } `; export function TodoItem(props: TodoItemProps) { const [isEditingItem, setIsEditingItem] = useState<boolean>(false); const [item, setNewItem] = useState<string | null>(null); return ( <FlexWrapper> <Input disabled={!isEditingItem} defaultValue={props.item} isEditing={isEditingItem} onChange={({ target }) => setNewItem(target.value)} /> {!isEditingItem && <Button onClick={() => setIsEditingItem(true)} > Edit </Button>} {isEditingItem && <Button onClick={async () => { await props.updateItem(props.id, item); //fetch updated items await props.fetchItems(); setIsEditingItem(false) }}> Update </Button>} <Button danger onClick={async () => { await props.deleteItem(props.id); //fetch updated items await await props.fetchItems(); }} > Delete </Button> </FlexWrapper> ); }
업데이트 기능을 위해 isEditingItem
상태가 false
일 때 비활성화되는 입력이 있습니다. "편집" 버튼을 클릭하면 isEditingItem
상태가 true
로 전환되고 "업데이트" 버튼이 표시됩니다. 여기에서 입력 구성 요소가 활성화되고 사용자가 새 값을 입력할 수 있습니다. "업데이트" 버튼을 클릭하면 전달된 매개변수로 updateItem
함수를 호출하고 isEditingItem
을 다시 false
로 토글합니다.
home
페이지 구성 요소에는 CRUD 작업을 수행하는 비동기 함수가 있습니다.
… const [items, setItems] = useState<Array<{ item: string; id: string }>>([]); const [newItem, setNewItem] = useState<string>(''); const fetchItems = async () => { try { const data = await fetch('/api/fetch'); const res = await data.json(); setItems(res.data); } catch (error) { console.log(error); } }; const createItem = async (item: string) => { try { const data = await fetch('/api', { method: 'POST', body: JSON.stringify({ item }), headers: { 'Content-Type': 'application/json', }, }); } catch (error) { console.log(error); } }; const deleteItem = async (id: string) => { try { const data = await fetch('/api', { method: 'DELETE', body: JSON.stringify({ id }), headers: { 'Content-Type': 'application/json', }, }); const res = await data.json(); alert(res.message); } catch (error) { console.log(error); } }; const updateItem = async (id: string, updatedItem: string) => { try { const data = await fetch('/api', { method: 'PATCH', body: JSON.stringify({ id, updatedItem }), headers: { 'Content-Type': 'application/json', }, }); const res = await data.json(); alert(res.message); } catch (error) { console.log(error); } }; useEffect(() => { fetchItems(); }, []); …
위의 코드 블록에는 서버에서 fetchItems
를 반환하는 todoArray
가 있습니다. 그런 다음 문자열을 사용하는 createItem
함수가 있습니다. 매개변수는 새 할 일 항목의 값입니다. updateItem
함수는 업데이트할 항목의 ID와 updatedItem
값이라는 두 개의 매개변수를 사용합니다. 그리고 deleteItem
함수는 전달된 ID와 일치하는 항목을 제거합니다.
할 일 항목을 렌더링하기 위해 items
상태를 매핑합니다.
… return ( <StyledHome> <h1>Welcome to Home!</h1> <TodoWrapper> {items.length > 0 && items.map((val) => ( <TodoItem key={val.id} item={val.item} id={val.id} deleteItem={deleteItem} updateItem={updateItem} fetchItems={fetchItems} /> ))} </TodoWrapper> <form onSubmit={async(e) => { e.preventDefault(); await createItem(newItem); //Clean up new item setNewItem(''); await fetchItems(); }} > <FlexWrapper> <Input value={newItem} onChange={({ target }) => setNewItem(target.value)} placeholder="Add new item…" /> <Button success type="submit"> Add + </Button> </FlexWrapper> </form> </StyledHome> ); …
이제 서버와 프런트 엔드가 설정되었습니다. npx nx serve todo-api
를 실행하여 API 애플리케이션을 제공할 수 있으며 Next.js 애플리케이션의 경우 npx nx serve todo
를 실행합니다. "계속" 버튼을 클릭하면 기본 할 일 항목이 표시된 페이지가 표시됩니다.
이제 하나의 작업 공간에서 작동하는 Next.js 및 Express 애플리케이션이 있습니다.
Nx에는 터미널 실행에서 애플리케이션의 종속성 그래프를 볼 수 있는 또 다른 CLI 도구가 있습니다. npx nx dep-graph
를 실행하면 애플리케이션의 종속성 그래프를 나타내는 아래 이미지와 유사한 화면이 표시됩니다.
Nx용 기타 CLI 명령
-
nx list
현재 설치된 Nx 플러그인을 나열합니다. -
nx migrate latest
package.json
의 패키지를 최신 버전으로 업데이트합니다. -
nx affected
영향을 받거나 수정된 앱에서만 작업을 수행합니다. -
nx run-many --target serve --projects todo-api,todo
나열된 모든 프로젝트에서 대상 명령을 실행합니다.
결론
Nx에 대한 일반적인 개요로서 이 기사에서는 Nx가 제공하는 기능과 Nx가 작업을 더 쉽게 만드는 방법을 다뤘습니다. 또한 Nx 작업 공간에서 Next.js 애플리케이션을 설정하고 기존 작업 공간에 Express 플러그인을 추가하고 monorepo 기능을 사용하여 작업 공간에서 둘 이상의 애플리케이션을 수용하는 과정을 살펴보았습니다.
GitHub 리포지토리에서 전체 소스 코드를 찾을 수 있습니다. Nx에 대한 추가 정보는 문서 또는 Next.js에 대한 Nx 문서를 확인하십시오.