Panduan Next.js Typescript
How To Run
Development
- Jalankan perintah
pnpm run dev
pada terminal - Buka browser dan akses alamat yang ditampilkan (biasanya
http://localhost:3000
)
Production
- Jalankan perintah
pnpm run build
untuk membuat build production - Jalankan perintah
pnpm start
untuk menjalankan server production
General Guidelines
- Use NextJS 14 dan app directory
- Prefer full client-side rendering unless SEO is needed
- Increase server resource usage
- Error handling is not well-defined yet
- Network debugging is difficult
Library
- Use react-query for fetcher library with
useQuery
oruseMutation
- Use ant design for form state library
Tips & Trick
- Minimize
useEffect
usage: Refer to You Might Not Need an Effect - Code Splitting: Manfaatkan dynamic import untuk code splitting
- Memoization: Gunakan
React.memo
untuk mencegah re-render komponen yang tidak perlu
File Naming Convention
- Hooks:
[nama-hooks].ts
dan context-aware (e.g.,use-users-query.ts
) - Page Components:
page.tsx
untuk halaman utama - Global Components:
[nama-component].tsx
(e.g.,navbar.tsx
,footer.tsx
) - Utils:
[nama-utils].ts
(e.g.,date-formatter.ts
,api-client.ts
) - Constants:
[nama-constants].ts
(e.g.,api-endpoints.ts
,form-config.ts
)
Folder Structure
src/app/
: Application logic, including components, pages, and API routes._components/
: Shared components._hooks
: Shared hooks.(authenticated)/
: Components and pages requiring authentication. See [[NextJS Development Guideline#Module Structure]] for more details.(public)/
: Publicly accessible components and pages. See [[NextJS Development Guideline#Module Structure]] for more detailsapi/
: API routes.
src/common/
: Common utilities, global type, enum, constant e.gsrc/common/types/response.ts
src/libs/
: Libraries and utilities.src/types/
: Declared type e.g:src/types/react-slider.d.ts
src/utils/
: Utility functions.src/api/
: API and its type definitions.src/middleware.ts
: Middleware functions.
Overview:
└── src/
├── app/
│ ├── _components/
│ ├── (authenticated)/
│ │ └── path/
│ │ ├── _components/
│ │ ├── _hooks/
│ │ ├── _types/
│ │ └── path/
│ │ ├── _components/
│ │ ├── _hooks/
│ │ ├── _types/
│ │ └── page.tsx
│ ├── (public)/
│ │ └── path/
│ │ ├── _components/
│ │ ├── _hooks/
│ │ └── path/
│ │ └── page.tsx
│ └── api/
├── common/
│ ├── enums/
│ ├── constants/
│ └── types/
├── libs/
├── types/
├── utils/
├── api/
└── middleware.ts
Module Structure
_components/
: Components within the module._hooks/
: Hooks within the module._const/
: Constant within the module_utils/
: Utils within the modulepage.ts
: Page component
Module Page Path Naming
- Create:
/create
- Update:
/[id]/update
- List:
/
- Detail
/[id]
How to create a module
Tentukan Nama dan Lokasi Module:
- Pilih nama yang deskriptif dan sesuai dengan fungsinya.
- Tentukan apakah module tersebut berada di dalam
(authenticated)
atau(public)
route. - Buat folder di dalam route yang sesuai (misalnya,
src/app/(authenticated)/users
).
Buat File
page.tsx
:- File ini akan menjadi entry point untuk module Anda.
- Implementasikan UI dan logika utama di dalam file ini.
// src/app/(authenticated)/users/page.tsx import React from "react"; import { Page } from "admiral"; const UsersPage = () => { return <Page title="List User">{/* ...Implementasi UI... */}</Page>; }; export default UsersPage;
Buat Constants (Jika Diperlukan):
- Jika module Anda memiliki konstanta, buat folder
_const
di dalam module Anda. - Letakkan konstanta tersebut di dalam folder
_const
.
// src/app/(authenticated)/users/_const/user-status-color.ts export const USER_STATUS_COLOR = { PENDING: "processing", SUCCESS: "success", ERROR: "error", };
- Jika module Anda memiliki konstanta, buat folder
Buat Komponen Pendukung (Jika Diperlukan):
- Jika module Anda memiliki komponen yang kompleks atau digunakan kembali, buat folder
_components
di dalam module Anda. - Letakkan komponen-komponen tersebut di dalam folder
_components
.
// src/app/(authenticated)/users/_components/user-datatable.tsx import React from "react"; import { Tag } from "antd"; import Datatable from "admiral/table/datatable/index"; import { ColumnsType } from "antd/es/table"; import { TUserItemList } from "@/api/user/type"; import { USER_STATUS_COLOR } from "../../_const/user-status-color"; const columns: ColumnsType<TUserItemList> = [ /*...*/ { dataIndex: "status", title: "Status", key: "status", sorter: true, sortOrder: makeSortOrder(filters, "is_active"), render: (status) => { return <Tag color={USER_STATUS_COLOR[status]}>{status}</Tag>; }, }, /*...*/ ]; const UserDatatable: typeof Datatable = (props) => { return <Datatable columns={columns} {...props} />; }; export default UserList;
- Jika module Anda memiliki komponen yang kompleks atau digunakan kembali, buat folder
Buat Hooks (Jika Diperlukan):
- Jika module Anda memiliki logika yang kompleks atau digunakan kembali, buat folder
_hooks
di dalam module Anda. - Letakkan hooks tersebut di dalam folder
_hooks
.
// src/app/(authenticated)/users/_hooks/use-users-query.ts import { getUsers } from "@/api/user"; import { TGetUsersParams } from "@/api/user/type"; import { useQuery } from "@/app/_hooks/request/use-query"; export const useUsersQuery = (params: TGetUsersParams) => { return useQuery({ queryKey: ["users", params], queryFn: () => getUsers(params), }); };
- Jika module Anda memiliki logika yang kompleks atau digunakan kembali, buat folder
Buat Utils (Jika Diperlukan):
- Jika module Anda memiliki fungsi utilitas, buat folder
_utils
di dalam module Anda. - Letakkan fungsi utilitas tersebut di dalam folder
_utils
.
// src/app/(authenticated)/users/_utils/format-user.ts const formatUser = (user) => { return { ...user, fullName: `${user.firstName} ${user.lastName}`, }; }; export default formatUser;
- Jika module Anda memiliki fungsi utilitas, buat folder
Overview
page.tsx
:- Import komponen, hooks, utils, atau konstanta yang Anda buat di dalam file
page.tsx
. - Gunakan komponen, hooks, utils, atau konstanta tersebut untuk mengimplementasikan UI dan logika module Anda.
// src/app/(authenticated)/users/page.tsx import React from "react"; import UserDatatable from "./_components/user-datatable"; import useUsersQuery from "./_hooks/use-users-query"; const UsersPage = () => { const users = useUsersQuery(); return ( <Page title="List User"> <UserDatatable source={users.data} /> </Page> ); }; export default UsersPage;
- Import komponen, hooks, utils, atau konstanta yang Anda buat di dalam file
How to set a permission for page
Definisikan Permissions
- Buat file
src/common/constants/permissions.ts
untuk mendefinisikan semua permission yang digunakan di aplikasi Anda.
// src/common/constants/permissions.ts
export const PERMISSIONS = {
user: {
read: "user.read",
write: "user.write",
},
// ...Definisikan permission lainnya...
};
Register Permission
Approach 1: Global layout
- Login
- Save token in cookie
- Fetch detail user using saved token
- Create schema page with permission support
const pagePermissionMapper = [
{
url: "/dashboard",
permissions: [PERMISSIONS.DASHBOARD],
},
{
url: "/users",
permissions: [PERMISSIONS.USER_READ],
},
];
- Check condition if user permission have access to page with defined permission, using global function
filterPermission
const pathname = usePathname();
const { data } = useAccount();
const router = useRouter();
useEffect(() => {
if (!data) return;
const allowedPage = filterPermission(pagePermissionMapper, (page) => {
return (
pathname.includes(page.url) &&
page.permissions.some((permission) =>
data.permissions.includes(permission)
)
);
});
if (!allowedPage.length) {
// Redirect to /dashboard if permission is not granted
router.replace("/dashboard");
}
}, [data?.id]);
Approach 2: Middlewarere
- Login
- Get token & fetch detail user
- Save token and permission dari user in cookie
- Create schema page with permission support
const pagePermissionMapper = [
{
url: "/dashboard",
permissions: [PERMISSIONS.DASHBOARD],
},
{
url: "/users",
permissions: [PERMISSIONS.USER_READ],
},
];
- Check condition if user permission have access to menu with defined permission, using global function
filterPermission
const isAuthorized = filterPermission(pagePermissionMapper, (page) => {
return (
url.pathname.includes(page.url) &&
page.permissions.some((permission) =>
session?.user?.role?.permissions.includes(permission)
)
);
});
if (!isAuthorized) {
return NextResponse.redirect("/dashboard");
}
How to set a permission for section
Gunakan Komponen
<Guard />
:- Bungkus section dengan
<Guard permissions={[/* permissions */]} fallback={/* fallback UI */}>
.
import { Guard } from "@/app/_components/guard"; import { PERMISSIONS } from "@/commons/constants/permissions"; const MyComponent = () => ( <div> <h1>My Component</h1> <Guard permissions={[PERMISSIONS.MY_SECTION.READ]} fallback={<p>No Access</p>} > {/* Protected Content */} </Guard> </div> ); export default MyComponent;
- Bungkus section dengan
Contoh Penggunaan:
{ title: "NIK", render: (_, record) => ( <Guard permissions={[PERMISSIONS.USER.READ]} fallback={record.employee?.emp_no}> <Link to={`/employee/${record.employee?.id}`}>{record.employee?.emp_no}</Link> </Guard> ), }
Penjelasan: Komponen
<Guard />
menampilkan konten jika pengguna memiliki izin yang sesuai, jika tidak, ia menampilkan UI fallback.
State Management
Pilih state management yang sesuai dengan kebutuhan kompleksitas aplikasi.
useState
:- Kegunaan: Untuk state lokal komponen yang sederhana dan jarang berubah.
useContext
:- Kegunaan: Untuk data global yang jarang berubah dan perlu diakses oleh banyak komponen (e.g., data user, theme)
react-query
:- Kegunaan: Untuk manajemen state yang berhubungan dengan data fetching dan caching.
Admiral
See example component in Link to admiral