We will introduce a simple Layered Architecture in Next.js to give you an idea of how to achieve a good separation of responsibilities and high maintenance code.
Tech Stack
The basic technology stack for target web applications using Next.js / TypeScript is as follows:
- Language
- Full Node.js & TypeScript
- Frontend
- Next.js : https://nextjs.org/docs/getting-started
- Recoil : https://recoiljs.org/
- Authorization
- Firebase Authentication : https://firebase.google.com/docs/auth
- Data Store
- Firebase Firestore: https://firebase.google.com/docs/firestore
- Infrastructure
- Vercel : https://vercel.com/docs
You can create an efficient service using only Node.js.
SSR / AMP of Next.js, Vercel, and Firebase make it fast and easy to scale.
Simplified Layered Architecture
https://martinfowler.com/bliki/PresentationDomainDataLayering.html
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
With reference to the above articles, we have adopted a simplified layered architecture.
About the responsibilities of each directory
Describes the responsibilities of each directory under the src directory.
- components : React Component based on Atomic Design
- foundations : Calling Firebase, Google Analytics library, etc.
- hooks : Common React Hooks across components
- interactors: responsible for external services and communication (fetcher and mapper)
- mappers : responsible for the mapping to each object
- models : Common types within an application
- pages : Responsible for app routing
- services : responsible for complex business logic
- store : Definition for state management in apps (Recoil)
How to Write a Component
When creating a React Component, write the View (JSX) as a component(Presenter), and the logic for displaying data as a Container, and separate the responsibilities.
import styled from 'styled-component';
import React, { memo } from 'react';
type ContainerProps = {
target?: string;
};
type Props = Required<ContainerProps>;
// Layers for display
export const Presenter: React.FC<Props> = (props) => (
<h1 className={css['greeting']}>
Welcome to, <span>{props.target}</span>
</h1>
);
// Logic layers
// Convert props to the data format displayed on Component
const Container: React.FC<ContainerProps> = (props) => {
const target = props.target || 'world';
return <Presenter target={target} />;
};
export default memo(Container);
Merit of the architecture
- Separating Presenter (Container) and Component makes the thinking simpler
- The responsibilities of each layer are clear, so there are fewer places to read and rewrite code when specifications are added or changed
- Do not worry about what and where to write when adding a process