A hands-on learning project to explore modern Next.js App Router patterns using the Jikan (MyAnimeList) API. The main focus is understanding how Next.js differs from plain React and how it simplifies real-world app flows.
- Goals
- Key Learnings
- Tech Stack
- Folder Structure
- Screenshots
- How to Run
- API Reference
- APIs and References
- Credits & Disclaimer
- Learn the App Router mindset in Next.js (layouts, server-first, routing).
- Practice data-fetching patterns and URL-driven state (no heavy client state).
- Implement practical UI features: search, filters, pagination, skeleton loading.
- Understand rendering strategies: static, dynamic, streaming.
- Handle errors gracefully with error and not-found routes.
- Server Components (default): fetch data securely on the server, no bundle cost on the client, great for SEO and performance.
- Client Components (with
"use client"
): needed for interactivity (inputs, onChange, onClick, router hooks). - Practical split used here:
- Server: building the board grid
- Client: search input, filter controls, pagination links
- Instead of local component state, the app encodes UI state in the URL.
- Flow learned and applied:
- Read current params with
useSearchParams
in client components - Update them using
URLSearchParams
+useRouter().replace()
- Server components re-render automatically based on
searchParams
- Read current params with
- Benefits: shareable URLs, back/forward nav works, SSR-friendly, fewer client states to manage.
- Pagination numbers generated with a helper that returns numbers and ellipses.
- Skeletons displayed while data is loading to keep layout stable.
- Filters (e.g.,
sfw
,type
,status
,rating
,order_by
,sort
) map to API query parameters and are encoded in the URL.
- Static: layout and some non-parameterized UI render once per route.
- Dynamic: pages/components depending on
searchParams
or dynamic data fetches. - Streaming: leverage async server components and Suspense to progressively render content and skeletons for faster perceived performance.
- Dedicated
error.tsx
andnot-found.tsx
routes handle failures. - Reset behavior falls back to reloading or navigating to the root when needed.
- Server component error UI uses a simple form submit to retry (no onClick in server components).
- Next.js 15 (App Router)
- TypeScript
- Bootstrap 5 (styling)
- next/image with remote patterns for
cdn.myanimelist.net
- Jikan API (MyAnimeList) v4
nextjs-anime-api/
├─ app/ # Route tree (App Router)
│ ├─ styles/ # Scoped CSS modules
│ │ ├─ styles.module.css
│ │ ├─ styles.board.module.css
│ │ ├─ styles.filters.module.css
│ │ ├─ styles.navbar.module.css
│ │ ├─ styles.pagination.module.css
│ │ ├─ styles.search.module.css
│ │ └─ styles.skeleton.module.css
│ ├─ ui/ # UI components for this route
│ │ ├─ board.tsx
│ │ ├─ filter.tsx
│ │ ├─ navbar.tsx
│ │ ├─ pagination.tsx
│ │ ├─ search.tsx
│ │ └─ skeletons.tsx
│ ├─ error.tsx # Error boundary (client)
│ ├─ layout.tsx # Root layout (server)
│ ├─ not-found.tsx # 404 boundary (server)
│ └─ page.tsx # Home route (server)
├─ public/
│ └─ screenshots/ # Project screenshots
├─ next.config.ts # next/image config, etc.
├─ postcss.config.mjs
├─ package.json
├─ pnpm-lock.yaml
├─ tsconfig.json
└─ learn.txt
Screens from the running app:
- Install dependencies
pnpm install
- Start development server
pnpm dev
Environment: none required for the public Jikan API.
Endpoint: GET https://api.jikan.moe/v4/anime
Param | Type | Allowed values / format | Description |
---|---|---|---|
unapproved |
boolean | flag only (?unapproved ) |
Include user-submitted entries not yet approved by MAL |
page |
integer | 1..n | Page number |
limit |
integer | 1..25 (API default 25) | Items per page |
q |
string | any | Search query (title text) |
type |
string | tv , movie , ova , special , ona , music , cm , pv , tv_special |
Filter by anime type |
score |
number | 0..10 | Exact score |
min_score |
number | 0..10 | Minimum score |
max_score |
number | 0..10 | Maximum score |
status |
string | airing , complete , upcoming |
Airing status |
rating |
string | g , pg , pg13 , r17 , r , rx |
Audience rating (G, PG, PG-13, R-17, R+, Rx) |
sfw |
boolean | true /false |
Exclude adult entries |
genres |
string | comma-separated IDs (e.g., 1,2,3 ) |
Include these genres |
genres_exclude |
string | comma-separated IDs | Exclude these genres |
order_by |
string | mal_id , title , start_date , end_date , episodes , score , scored_by , rank , popularity , members , favorites |
Sort field |
sort |
string | desc , asc |
Sort direction |
letter |
string | single letter | Title starting letter |
producers |
string | comma-separated IDs | Filter by producers |
start_date |
string | YYYY or YYYY-MM or YYYY-MM-DD |
Start date filter |
end_date |
string | YYYY or YYYY-MM or YYYY-MM-DD |
End date filter |
GET https://api.jikan.moe/v4/anime?q=death&type=tv&sfw=true&order_by=score&sort=desc&page=1&limit=12
{
"pagination": {
"last_visible_page": 1000,
"has_next_page": true,
"items": { "count": 12, "total": 12000, "per_page": 12 }
},
"data": [
{
"mal_id": 5114,
"url": "https://myanimelist.net/anime/5114/Fullmetal_Alchemist__Brotherhood",
"images": {
"jpg": { "image_url": "https://cdn.myanimelist.net/.../image.jpg" },
"webp": { "image_url": "https://cdn.myanimelist.net/.../image.webp" }
},
"title": "Fullmetal Alchemist: Brotherhood",
"title_english": "",
"title_japanese": "鋼の錬金術師",
"type": "tv",
"episodes": 64,
"status": "Finished Airing",
"rating": "R - 17+",
"score": 9.1,
"scored_by": 2000000,
"rank": 1,
"popularity": 1,
"members": 3800000,
"favorites": 250000,
"year": 2009,
"aired": { "from": "2009-04-05", "to": "2010-07-04" },
"synopsis": "Two brothers search for the Philosopher's Stone...",
"producers": [{ "mal_id": 23, "name": "Aniplex" }],
"genres": [{ "mal_id": 1, "name": "Action" }]
}
]
}
- Next.js Docs (App Router, Data Fetching, Streaming, Error Handling)
- Jikan (MyAnimeList) API v4:
https://api.jikan.moe/v4
(used for anime data) - next/image remote patterns to allow
cdn.myanimelist.net/images/**
- Styling and layout were refined with the help of AI assistants (including Cursor AI). I iterated on prompts and applied manual adjustments to fit the project’s visual style.
- Based on learnings from official Next.js tutorials and docs, plus exploration of URL-driven state patterns.
- Data courtesy of the Jikan API. This project is for learning/demonstration purposes only; no affiliation with MyAnimeList.
If you’re using this as a study reference, I recommend reading through the component files for examples of server vs client components, URLSearchParams-driven state, and pagination/skeleton patterns with streaming.