diff --git a/.all-contributorsrc b/.all-contributorsrc
index 32b2e8282a..5c3deaf428 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -7,7 +7,7 @@
"login": "sergeysova",
"name": "Sergey Sova",
"avatar_url": "https://avatars.githubusercontent.com/u/5620073?v=4",
- "profile": "https://sova.dev/",
+ "profile": "https://sergeysova.com/",
"contributions": [
"blog",
"doc",
diff --git a/.stylelintrc.js b/.stylelintrc.js
index 1b8c30867e..a2d6caeb49 100644
--- a/.stylelintrc.js
+++ b/.stylelintrc.js
@@ -1,12 +1,11 @@
module.exports = {
extends: [
- "stylelint-config-standard-scss",
"stylelint-config-recommended",
+ "stylelint-config-standard-scss",
"stylelint-config-recess-order",
],
rules: {
"color-hex-length": "long",
- "at-rule-no-unknown": true,
"selector-class-pattern": null,
},
};
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fc94ad32f..9a6a6b8a57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,17 +5,71 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## [Since last release][since-last-release]
+
-### Added
+## [2.1.0] - 2024-10-31
+
+The new revision of Feature-Sliced Design is here! The main difference with FSD 2.0 is the new approach to decomposition — “pages first”.
+
+### What's “pages-first”?
+
+You do “pages first” by keeping more code in pages. For example, large blocks of UI, forms and data logic that are not reused on other pages should now stay in the slice of the page that they are used in. The division by segments (`ui`, `api`, `model`, etc.) still applies to all this code, and we encourage you to further split and organize code into folders inside of segments — don't just pile all the code into a single file.
+
+In the same way, widgets are no longer just a compositional layer, instead they should also store code that isn't currently needed outside of that widget, including its own stores, business logic, and API interactions.
+
+When you have a need to reuse code in several widgets or pages, consider putting it in Shared. If that code involves business logic (i. e. managing specific modal dialogs), consider breaking it up into infrastructural code, like the modal manager, and the business code, like the content of the modals. The infrastructure can then go to Shared, and the content can stay in the pages that use this infrastructure.
+
+### How is it different?
+
+In FSD 2.0 we explained how to identify entities and features in your application, and then combine them in widgets and pages. Over time we started disliking this approach, mostly for the following reasons:
+
+- Code cohesion is much worse in this approach
+ - You need to jump around several folders just to make changes to a single user flow
+ - Unused code is harder to delete because it's somewhere else
+- Finding entities and features is still an advanced skill that needs to be developed over time
+ - It requires understanding of the business context, which not all developers want to bother with
+ - On the other hand, splitting by pages is natural and requires little training
+ - Different developers have different understandings of these concepts, which leads to everyone having their own idea of FSD, which causes conflict and misunderstanding
+
+### Is it hard to migrate from FSD 2.0?
+
+This is a non-breaking change, so you don’t even necessarily need to migrate your current FSD projects to FSD 2.1, but we still think the new way of thinking will lead to a more cohesive and less opinionated structure. We’ve compiled a few steps you can take in [the migration guide](https://feature-sliced.design/docs/guides/migration/from-v2-0).
-- New article about how to use FSD with Next.js (#644).
+### What else happened since the last release?
+
+The cross-import notation (`@x`) that was an experimental proposal for a long time has now been standardized! Its official name is **Public API for cross-imports**. You can use it to create explicit connections between entities. There's [a new section in our documentation all about this new notation](https://feature-sliced.design/docs/reference/public-api#public-api-for-cross-imports).
+
+Another exciting new thing in the FSD ecosystem is our architectural linter, [Steiger](https://github.com/feature-sliced/steiger). It's still in active development, but it is production-ready.
+
+A couple more minor clarifications to the docs were made as well:
+
+1. Application-aware things like the route constants, the API calls, or company logo, are now explicitly allowed in Shared. Business logic is still not allowed, but these things are not considered to be business logic.
+2. Imports between segments in App and Shared were always allowed, but it's been made explicit too.
+
+And here's what happened to the documentation website:
+
+#### Added
+
+- Slightly rewritten and expanded overview page to give some details about FSD right away (#685).
+- New partial translations: Korean (#739, #736, #735, #742, #732, #730, #715), Japanese (#728).
- The tutorial was rewritten. Technical details were stripped out, more FSD theory has been added (#665).
+- Guides on how to deal with common frontend issues like page layouts (#708), types (#701), authentication (#693).
+- Guides on how to use FSD with Nuxt (#710, #689, #683, #679), SvelteKit (#698), Next.js (#699, #664, #644), and TanStack Query (#673).
+- A new feedback widget, powered by PushFeedback! Go give it a try and let us know what you think of the new pages (#695).
+- Comparison of FSD with Atomic Design (#671).
+
+#### Changed
+
+- The migration guide from a custom architecture (formerly known as "from legacy") has been actualized (#725).
+
+#### Removed
+
+- The decomposition cheatsheet is now unlisted for an undefined period of time. It proved to be more harmful than useful, but maybe it can be saved later (#649).
## [2.0.0] - 2023-10-01
> **Note**
-> This release note is retrospective, meaning that prior to this release, the Feature-Sliced Design project did not keep a changelog. Below is a summary of the most prominent recent changes, but there is no FSD v1. Prior to FSD, there has been a project called ["Feature Slices"](https://featureslices.dev/v1.0.html), and it is considered to be the v1 of FSD.
+> This release note is retrospective, meaning that prior to this release, the Feature-Sliced Design project did not keep a changelog. Below is a summary of the most prominent recent changes, but there is no FSD v1. Prior to FSD, there has been a project called ["Feature Slices"](https://feature-sliced.github.io/featureslices.dev/v1.0.html), and it is considered to be the v1 of FSD.
### Deprecated
@@ -41,5 +95,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The overview page has been rewritten to be more concise and informative (#512, #515, #516).
- FSD has updated its branding, and there are now guidelines to the brand usage. The standard spelling of the name is now "Feature-Sliced Design" (#496, #499, #500, #465).
-[since-last-release]: https://github.com/feature-sliced/documentation/compare/v2.0.0...HEAD
+[since-last-release]: https://github.com/feature-sliced/documentation/compare/v2.1.0...HEAD
+[2.1.0]: https://github.com/feature-sliced/documentation/releases/tag/v2.1.0
[2.0.0]: https://github.com/feature-sliced/documentation/releases/tag/v2.0.0
diff --git a/DEV.md b/DEV.md
index c9c70bdf51..c540678a38 100644
--- a/DEV.md
+++ b/DEV.md
@@ -7,6 +7,7 @@ This website is built using [Docusaurus 2](https://docusaurus.io/), a modern sta
- [Russian docs version](i18n/ru)
- [English docs version](i18n/en)
- [Uzbek docs version](i18n/uz)
+- [Japanese docs version](i18n/ja)
## Installation
@@ -20,6 +21,8 @@ pnpm install
pnpm start # for default locale
pnpm start:ru # for RU locale
pnpm start:en # for EN locale
+pnpm start:uz # for UZ locale
+pnpm start:ja # for JA locale
```
> About [docusaurus/i18n commands](https://docusaurus.io/docs/i18n/git#translate-the-files)
diff --git a/README.md b/README.md
index 7a2ab24c35..e8792d4bdb 100644
--- a/README.md
+++ b/README.md
@@ -112,7 +112,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index dd249ac168..0000000000
--- a/babel.config.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
-};
diff --git a/config/docusaurus/extensions.js b/config/docusaurus/extensions.js
index fd52101074..1909f746b1 100644
--- a/config/docusaurus/extensions.js
+++ b/config/docusaurus/extensions.js
@@ -43,7 +43,7 @@ const presets = [
showLastUpdateTime: true,
versions: {
current: {
- label: `v2.0.0 🍰`,
+ label: `v2.1`,
},
},
sidebarItemsGenerator,
diff --git a/config/docusaurus/i18n.js b/config/docusaurus/i18n.js
index bea96e6a50..2cef5f9050 100644
--- a/config/docusaurus/i18n.js
+++ b/config/docusaurus/i18n.js
@@ -3,7 +3,7 @@ const { DEFAULT_LOCALE } = require("./consts");
/** @type {import('@docusaurus/types').DocusaurusConfig["i18n"]} */
const i18n = {
defaultLocale: DEFAULT_LOCALE,
- locales: ["ru", "en", "uz", "kr"],
+ locales: ["ru", "en", "uz", "kr", "ja"],
localeConfigs: {
ru: {
label: "Русский",
@@ -17,6 +17,9 @@ const i18n = {
kr: {
label: "한국어",
},
+ ja: {
+ label: "日本語",
+ },
},
};
diff --git a/config/docusaurus/navbar.js b/config/docusaurus/navbar.js
index a0fe088f8e..4230a532ef 100644
--- a/config/docusaurus/navbar.js
+++ b/config/docusaurus/navbar.js
@@ -37,11 +37,11 @@ const navbar = {
dropdownActiveClassDisabled: true,
dropdownItemsAfter: [
{
- to: "https://featureslices.dev/v1.0.html",
+ to: "https://feature-sliced.github.io/featureslices.dev/v1.0.html",
label: "v1.0",
},
{
- to: "https://featureslices.dev/v0.1.html",
+ to: "https://feature-sliced.github.io/featureslices.dev/v0.1.html",
label: "v0.1",
},
{
diff --git a/config/docusaurus/routes.js b/config/docusaurus/routes.js
index 9051a065ec..1a657eabbe 100644
--- a/config/docusaurus/routes.js
+++ b/config/docusaurus/routes.js
@@ -10,7 +10,7 @@ const SECTIONS = {
},
MIGRATION: {
shortPath: "/docs/guides/migration",
- fullPath: "/docs/guides/migration/from-legacy",
+ fullPath: "/docs/guides/migration/from-custom",
},
};
@@ -109,17 +109,17 @@ const LEGACY_ROUTES = [
{
title: "Decouple of entities",
from: "/docs/concepts/decouple-entities",
- to: "/docs/reference/isolation/decouple-entities",
+ to: "/docs/reference/layers#import-rule-on-layers",
},
{
title: "Low Coupling & High Cohesion",
from: "/docs/concepts/low-coupling",
- to: "/docs/reference/isolation/coupling-cohesion",
+ to: "/docs/reference/slices-segments#zero-coupling-high-cohesion",
},
{
title: "Cross-communication",
from: "/docs/concepts/cross-communication",
- to: "/docs/reference/isolation",
+ to: "/docs/reference/layers#import-rule-on-layers",
},
{
title: "App splitting",
@@ -249,7 +249,7 @@ const LEGACY_ROUTES = [
{
title: "Migration from Legacy",
from: "/docs/guides/migration-from-legacy",
- to: "/docs/guides/migration/from-legacy",
+ to: "/docs/guides/migration/from-custom",
},
],
},
@@ -264,6 +264,30 @@ const LEGACY_ROUTES = [
},
],
},
+ {
+ group: "Rename 'legacy' to 'custom'",
+ details:
+ "'Legacy' is derogatory, we don't get to call people's projects legacy",
+ children: [
+ {
+ title: "Rename 'legacy' to custom",
+ from: "/docs/guides/migration/from-legacy",
+ to: "/docs/guides/migration/from-custom",
+ },
+ ],
+ },
+ {
+ group: "Deduplication of Reference",
+ details:
+ "Cleaned up the Reference section and deduplicated the material",
+ children: [
+ {
+ title: "Isolation of modules",
+ from: "/docs/reference/isolation",
+ to: "/docs/reference/layers#import-rule-on-layers",
+ },
+ ],
+ },
];
// @returns { from, to }[]
@@ -314,7 +338,7 @@ const _TOTAL_ROUTES = [
"/docs/guides/examples/theme",
"/docs/guides/examples/types",
"/docs/guides/examples/white-labels",
- "/docs/guides/migration/from-legacy",
+ "/docs/guides/migration/from-custom",
"/docs/guides/migration/from-v1",
"/docs/guides/tech/with-nextjs",
"/docs/",
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 798c2ddb50..20c3d1fb16 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -57,6 +57,9 @@ module.exports = {
darkTheme: prismThemes.oneDark,
},
},
+ future: {
+ experimental_faster: true,
+ },
};
// Remove configs if there are not secrets passed
diff --git a/i18n/en/docusaurus-plugin-content-docs/current.json b/i18n/en/docusaurus-plugin-content-docs/current.json
index 9af1eb6531..599055a3cf 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current.json
+++ b/i18n/en/docusaurus-plugin-content-docs/current.json
@@ -1,6 +1,6 @@
{
"version.label": {
- "message": "v2.0.0 🍰",
+ "message": "v2.1",
"description": "The label for version current"
},
"sidebar.getstartedSidebar.category.Tutorials": {
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md
index a09a1600a1..2f9b4bda5b 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md
+++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md
@@ -13,7 +13,7 @@ You can ask your question in our [Telegram chat][telegram], [Discord community][
### Is there a toolkit or a linter?
-There is an official ESLint config — [@feature-sliced/eslint-config][eslint-config-official], and an ESLint plugin — [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti], created by Aleksandr Belous, a community member. You're welcome to contribute to these projects or start your own!
+Yes! We have a linter called [Steiger][ext-steiger] to check your project's architecture and [folder generators][ext-tools] through a CLI or IDEs.
### Where to store the layout/template of pages?
@@ -58,10 +58,10 @@ Rather yes than no
Answered [here](/docs/guides/examples/auth)
+[ext-steiger]: https://github.com/feature-sliced/steiger
+[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools
[import-rule-layers]: /docs/reference/layers#import-rule-on-layers
[reference-entities]: /docs/reference/layers#entities
-[eslint-config-official]: https://github.com/feature-sliced/eslint-config
-[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced
[motivation]: /docs/about/motivation
[telegram]: https://t.me/feature_sliced
[discord]: https://discord.gg/S8MzWTUsmp
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.mdx
index a35f521066..2083b8cde5 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.mdx
+++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/overview.mdx
@@ -65,15 +65,15 @@ Layers, slices, and segments form a hierarchy like this:
Layers are standardized across all FSD projects. You don't have to use all of the layers, but their names are important. There are currently seven of them (from top to bottom):
-1. App\* — everything that makes the app run — routing, entrypoints, global styles, providers.
-2. Processes (deprecated) — complex inter-page scenarios.
-3. Pages — full pages or large parts of a page in nested routing.
-4. Widgets — large self-contained chunks of functionality or UI, usually delivering an entire use case.
-5. Features — _reused_ implementations of entire product features, i.e. actions that bring business value to the user.
-6. Entities — business entities that the project works with, like `user` or `product`.
-7. Shared\* — reusable functionality, especially when it's detached from the specifics of the project/business, though not necessarily.
+1. **App\*** — everything that makes the app run — routing, entrypoints, global styles, providers.
+2. **Processes** (deprecated) — complex inter-page scenarios.
+3. **Pages** — full pages or large parts of a page in nested routing.
+4. **Widgets** — large self-contained chunks of functionality or UI, usually delivering an entire use case.
+5. **Features** — _reused_ implementations of entire product features, i.e. actions that bring business value to the user.
+6. **Entities** — business entities that the project works with, like `user` or `product`.
+7. **Shared\*** — reusable functionality, especially when it's detached from the specifics of the project/business, though not necessarily.
-_\* — these layers, App and Shared, unlike the other layers, don't have slices, and are made up of segments directly._
+_\* — these layers, **App** and **Shared**, unlike the other layers, don't have slices, and are made up of segments directly._
The trick with layers is that modules on one layer can only know about and import from modules from the layers strictly below.
@@ -131,7 +131,7 @@ It's advised to refrain from adding new large entities while refactoring or refa
[tutorial]: /docs/get-started/tutorial
[examples]: /examples
-[migration]: /docs/guides/migration/from-legacy
+[migration]: /docs/guides/migration/from-custom
[ext-steiger]: https://github.com/feature-sliced/steiger
[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools
[ext-telegram]: https://t.me/feature_sliced
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md
index 346217a6a5..662020d924 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md
+++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md
@@ -38,7 +38,7 @@ As such, our Pages folder will look like this:
The key difference of Feature-Sliced Design from an unregulated code structure is that pages cannot reference each other. That is, one page cannot import code from another page. This is due to the **import rule on layers**:
-*A module in a slice can only import other slices when they are located on layers strictly below.*
+*A module (file) in a slice can only import other slices when they are located on layers strictly below.*
In this case, a page is a slice, so modules (files) inside this page can only reference code from layers below, not from the same layer, Pages.
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md
index 5a96d32275..bf8a33b9e2 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md
+++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md
@@ -189,7 +189,7 @@ To store the token in the User entity, create a reactive store in the `model` se
Since the API client is usually defined in `shared/api` or spreaded across the entities, the main challenge to this approach is making the token available to other requests that need it without breaking [the import rule on layers][import-rule-on-layers]:
-> A module in a slice can only import other slices when they are located on layers strictly below.
+> A module (file) in a slice can only import other slices when they are located on layers strictly below.
There are several solutions to this challenge:
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.md
index cd6390be4b..78e3748c6b 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.md
+++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/types.md
@@ -305,7 +305,7 @@ export const slice = createSlice({
extraReducers: (builder) => {
builder.addCase(fetchSong.fulfilled, (state, action) => {
// And handle the same fetch result by inserting the artists here
- usersAdapter.upsertMany(state, action.payload.users)
+ artistAdapter.upsertMany(state, action.payload.artists)
})
},
})
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/index.mdx
index 17d7beeeca..319ebbf93f 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/guides/index.mdx
+++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/index.mdx
@@ -25,10 +25,10 @@ import { ToolOutlined, ImportOutlined, BugOutlined, FunctionOutlined } from "@an
/>
+ 📁 src
+
+
+
+ 📁 actions
+
+
📁 product
+
📁 order
+
+
+
+
📁 api
+
📁 components
+
📁 containers
+
📁 constants
+
📁 i18n
+
📁 modules
+
📁 helpers
+
+
+ 📁 routes
+
+
📁 products.jsx
+
📄 products.[id].jsx
+
+
+
+
📁 utils
+
📁 reducers
+
📁 selectors
+
📁 styles
+
📄 App.jsx
+
📄 index.js
+
+
+
+## Before you start {#before-you-start}
+
+The most important question to ask your team when considering to switch to Feature-Sliced Design is — _do you really need it?_ We love Feature-Sliced Design, but even we recognize that some projects are perfectly fine without it.
+
+Here are some reasons to consider making the switch:
+
+1. New team members are complaining that it's hard to get to a productive level
+2. Making modifications to one part of the code **often** causes another unrelated part to break
+3. Adding new functionality is difficult due to the sheer amount of things you need to think about
+
+**Avoid switching to FSD against the will of your teammates**, even if you are the lead.
+First, convince your teammates that the benefits outweigh the cost of migration and the cost of learning a new architecture instead of the established one.
+
+Also keep in mind that any kind of architectural changes are not immediately observable to the management. Make sure they are on board with the switch before starting and explain to them why it might benefit the project.
+
+:::tip
+
+If you need help convincing the project manager that FSD is beneficial, consider some of these points:
+1. Migration to FSD can happen incrementally, so it will not halt the development of new features
+2. A good architecture can significantly decrease the time that a new developer needs to get productive
+3. FSD is a documented architecture, so the team doesn't have to continuously spend time on maintaining their own documentation
+
+:::
+
+---
+
+If you made the decision to start migrating, then the first thing you want to do is to set up an alias for `📁 src`. It will be helpful later to refer to top-level folders. We will consider `@` as an alias for `./src` for the rest of this guide.
+
+## Step 1. Divide the code by pages {#divide-code-by-pages}
+
+Most custom architectures already have a division by pages, however small or large in logic. If you already have `📁 pages`, you may skip this step.
+
+If you only have `📁 routes`, create `📁 pages` and try to move as much component code from `📁 routes` as possible. Ideally, you would have a tiny route and a larger page. As you're moving code, create a folder for each page and add an index file:
+
+:::note
+
+For now, it's okay if your pages reference each other. You can tackle that later, but for now, focus on establishing a prominent division by pages.
+
+:::
+
+Route file:
+
+```js title="src/routes/products.[id].js"
+export { ProductPage as default } from "@/pages/product"
+```
+
+Page index file:
+
+```js title="src/pages/product/index.js"
+export { ProductPage } from "./ProductPage.jsx"
+```
+
+Page component file:
+
+```jsx title="src/pages/product/ProductPage.jsx"
+export function ProductPage(props) {
+ return ;
+}
+```
+
+## Step 2. Separate everything else from the pages {#separate-everything-else-from-pages}
+
+Create a folder `📁 src/shared` and move everything that doesn't import from `📁 pages` or `📁 routes` there. Create a folder `📁 src/app` and move everything that does import the pages or routes there, including the routes themselves.
+
+Remember that the Shared layer doesn't have slices, so it's fine if segments import from each other.
+
+You should end up with a file structure like this:
+
+
+ 📁 src
+
+
+
+ 📁 app
+
+
+
+ 📁 routes
+
+
📄 products.jsx
+
📄 products.[id].jsx
+
+
+
+
📄 App.jsx
+
📄 index.js
+
+
+
+
+
+ 📁 pages
+
+
+
+ 📁 product
+
+
+
+ 📁 ui
+
+
📄 ProductPage.jsx
+
+
+
+
📄 index.js
+
+
+
+
📁 catalog
+
+
+
+
+
+ 📁 shared
+
+
📁 actions
+
📁 api
+
📁 components
+
📁 containers
+
📁 constants
+
📁 i18n
+
📁 modules
+
📁 helpers
+
📁 utils
+
📁 reducers
+
📁 selectors
+
📁 styles
+
+
+
+
+
+
+## Step 3. Tackle cross-imports between pages {#tackle-cross-imports-between-pages}
+
+
+
+
+Find all instances where one page is importing from the other and do one of the two things:
+
+1. Copy-paste the imported code into the depending page to remove the dependency
+2. Move the code to a proper segment in Shared:
+ - if it's a part of the UI kit, move it to `📁 shared/ui`;
+ - if it's a configuration constant, move it to `📁 shared/config`;
+ - if it's a backend interaction, move it to `📁 shared/api`.
+
+:::note
+
+**Copy-pasting isn't architecturally wrong**, in fact, sometimes it may be more correct to duplicate than to abstract into a new reusable module. The reason is that sometimes the shared parts of pages start drifting apart, and you don't want dependencies getting in your way in these cases.
+
+However, there is still sense in the DRY ("don't repeat yourself") principle, so make sure you're not copy-pasting business logic. Otherwise you will need to remember to fix bugs in several places at once.
+
+:::
+
+## Step 4. Unpack the Shared layer {#unpack-shared-layer}
+
+You might have a lot of stuff in the Shared layer on this step, and you generally want to avoid that. The reason is that the Shared layer may be a dependency for any other layer in your codebase, so making changes to that code is automatically more prone to unintended consequences.
+
+Find all the objects that are only used on one page and move it to the slice of that page. And yes, _that applies to actions, reducers, and selectors, too_. There is no benefit in grouping all actions together, but there is benefit in colocating relevant actions close to their usage.
+
+You should end up with a file structure like this:
+
+
+ 📁 src
+
+
📁 app (unchanged)
+
+
+ 📁 pages
+
+
+
+ 📁 product
+
+
📁 actions
+
📁 reducers
+
📁 selectors
+
+
+ 📁 ui
+
+
📄 Component.jsx
+
📄 Container.jsx
+
📄 ProductPage.jsx
+
+
+
+
📄 index.js
+
+
+
+
📁 catalog
+
+
+
+
+
+ 📁 shared (only objects that are reused)
+
+
📁 actions
+
📁 api
+
📁 components
+
📁 containers
+
📁 constants
+
📁 i18n
+
📁 modules
+
📁 helpers
+
📁 utils
+
📁 reducers
+
📁 selectors
+
📁 styles
+
+
+
+
+
+
+## Step 5. Organize code by technical purpose {#organize-by-technical-purpose}
+
+In FSD, division by technical purpose is done with _segments_. There are a few common ones:
+
+- `ui` — everything related to UI display: UI components, date formatters, styles, etc.
+- `api` — backend interactions: request functions, data types, mappers, etc.
+- `model` — the data model: schemas, interfaces, stores, and business logic.
+- `lib` — library code that other modules on this slice need.
+- `config` — configuration files and feature flags.
+
+You can create your own segments, too, if you need. Make sure not to create segments that group code by what it is, like `components`, `actions`, `types`, `utils`. Instead, group the code by what it's for.
+
+Reorganize your pages to separate code by segments. You should already have a `ui` segment, now it's time to create other segments, like `model` for your actions, reducers, and selectors, or `api` for your thunks and mutations.
+
+Also reorganize the Shared layer to remove these folders:
+- `📁 components`, `📁 containers` — most of it should become `📁 shared/ui`;
+- `📁 helpers`, `📁 utils` — if there are some reused helpers left, group them together by function, like dates or type conversions, and move theses groups to `📁 shared/lib`;
+- `📁 constants` — again, group by function and move to `📁 shared/config`.
+
+## Optional steps {#optional-steps}
+
+### Step 6. Form entities/features from Redux slices that are used on several pages {#form-entities-features-from-redux}
+
+Usually, these reused Redux slices will describe something relevant to the business, for example, products or users, so these can be moved to the Entities layer, one entity per one folder. If the Redux slice is related to an action that your users want to do in your app, like comments, then you can move it to the Features layer.
+
+Entities and features are meant to be independent from each other. If your business domain contains inherent connections between entities, refer to the [guide on business entities][business-entities-cross-relations] for advice on how to organize these connections.
+
+The API functions related to these slices can stay in `📁 shared/api`.
+
+### Step 7. Refactor your modules {#refactor-your-modules}
+
+The `📁 modules` folder is commonly used for business logic, so it's already pretty similar in nature to the Features layer from FSD. Some modules might also be describe large chunks of the UI, like an app header. In that case, you should migrate them to the Widgets layer.
+
+### Step 8. Form a clean UI foundation in `shared/ui` {#form-clean-ui-foundation}
+
+`📁 shared/ui` should ideally contain a set of UI elements that don't have any business logic encoded in them. They should also be highly reusable.
+
+Refactor the UI components that used to be in `📁 components` and `📁 containers` to separate out the business logic. Move that business logic to the higher layers. If it's not used in too many places, you could even consider copy-pasting.
+
+## See also {#see-also}
+
+- [(Talk in Russian) Ilya Klimov — Крысиные бега бесконечного рефакторинга: как не дать техническому долгу убить мотивацию и продукт](https://youtu.be/aOiJ3k2UvO4)
+
+[ext-steiger]: https://github.com/feature-sliced/steiger
+[business-entities-cross-relations]: /docs/guides/examples/types#business-entities-and-their-cross-references
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx
deleted file mode 100644
index 67ee065c12..0000000000
--- a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx
+++ /dev/null
@@ -1,120 +0,0 @@
----
-sidebar_position: 3
-sidebar_class_name: sidebar-item--wip
----
-
-import WIP from '@site/src/shared/ui/wip/tmpl.mdx'
-
-# Migration from legacy
-
-
-
-> The article aggregates the experience of several companies and projects on moving to Feature-Sliced Design with different initial conditions
-
-## Why?
-
-> How much does the move need? "Death by a thousand cuts" and those debt. What is missing? How can the methodology help?
-
-> See the talk of [Ilya Klimov about the need and procedure for refactoring](http://youtu.be/aOiJ3k2UvO4)
-
-![approaches-themed-bordered](/img/approaches.png)
-
-## What's the plan?
-
-### 1. Unification of the code base
-
-```diff
-- ├── products/
-- | ├── components/
-- | ├── containers/
-- | ├── store/
-- | ├── styles/
-- ├── checkout/
-- | ├── components/
-- | ├── containers/
-- | ├── helpers/
-- | ├── styles/
-+ └── src/
- ├── actions/
- ├── api/
-+ ├── components/
-+ ├── containers/
- ├── constants/
- ├── epics/
-+ ├── i18n/
- ├── modules/
-+ ├── helpers/
-+ ├── pages/
-- ├── routes/
-- ├── utils/
- ├── reducers/
-- ├── redux/
- ├── selectors/
-+ ├── store
-+ ├── styles/
- ├── App.jsx
- └── index.jsx
-```
-
-### 2. Putting together the destructive decoupled
-
-```diff
- └── src/
-- ├── actions/
- ├── api/
-- ├── components/
-- ├── containers/
-- ├── constants/
-- ├── epics/
-+ ├── entities/{...}
-+ | ├── ui
-+ | ├── model/{actions, selectors, ...}
-+ | ├── lib
- ├── i18n/
- | # We can temporarily put the remaining segments here
-+ ├── modules/{helpers, constants}
-- ├── helpers/
- ├── pages/
-- ├── reducers/
-- ├── selectors/
-- ├── store/
- ├── styles/
- ├── App.jsx
- └── index.jsx
-```
-
-### 3. Allocate scopes of responsibility
-
-```diff
- └── src/
-- ├── api/
-+ ├── app/
-+ | ├── index.jsx
-+ | ├── style.css
- ├── pages/
-+ ├── features/
-+ | ├── add-to-cart/{ui, model, lib}
-+ | ├── choose-delivery/{ui, model, lib}
-+ ├── entities/{...}
-+ | ├── delivery/{ui, model, lib}
-+ | ├── cart/{ui, model, lib}
-+ | ├── product/{ui, model, lib}
-+ ├── shared/
-+ | ├── api/
-+ | ├── lib/ # helpers
-+ | | ├── i18n/
-+ | ├── config/ # constants
-- ├── i18n/
-- ├── modules/{helpers, constants}
- └── index.jsx
-```
-
-### 4. Final ?
-
-> About the remaining problems and how much it is worth eliminating them
-
-## See also
-
-- [(Talk) Ilya Klimov-The Rat Race of endless refactoring: how not to let technical debt kill motivation and product](https://youtu.be/aOiJ3k2UvO4)
-- [(Talk) Ilya Azin - Architecture of Frontend projects](https://youtu.be/SnzPAr_FJ7w)
- - There is also discussed approaches for architecture and costs of refactoring
\ No newline at end of file
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md
index 3bd4324d5b..4c0a98a649 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md
+++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md
@@ -1,8 +1,8 @@
---
-sidebar_position: 4
+sidebar_position: 2
---
-# Migration from v1
+# Migration from v1 to v2
## Why v2?
@@ -158,10 +158,10 @@ Now it is much easier to [observe the principle of low coupling][refs-low-coupli
- [New ideas v2 with explanations (atomicdesign-chat)][ext-tg-v2-draft]
- [Discussion of abstractions and naming for the new version of the methodology (v2)](https://github.com/feature-sliced/documentation/discussions/31)
-[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion
+[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion
[refs-adaptability]: /docs/about/understanding/naming
-[ext-v1]: https://featureslices.dev/v1.0.html
+[ext-v1]: https://feature-sliced.github.io/featureslices.dev/v1.0.html
[ext-tg-spb]: https://t.me/feature_slices
[ext-fdd]: https://github.com/feature-sliced/documentation/tree/rc/feature-driven
[ext-fdd-issues]: https://github.com/kof/feature-driven-architecture/issues
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md
new file mode 100644
index 0000000000..af057a421e
--- /dev/null
+++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md
@@ -0,0 +1,45 @@
+---
+sidebar_position: 3
+---
+
+# Migration from v2.0 to v2.1
+
+The main change in v2.1 is the new mental model for decomposing an interface — pages first.
+
+In v2.0, FSD would recommend identifying entities and features in your interface, considering even the smallest bits of entity representation and interactivity for decomposition. Then you would build widgets and pages from entities and features. In this model of decomposition, most of the logic was in entities and features, and pages were just compositional layers that didn't have much significance on their own.
+
+In v2.1, we recommend starting with pages, and possibly even stopping there. Most people already know how to separate the app into individual pages, and pages are also a common starting point when trying to locate a component in the codebase. In this new model of decomposition, you keep most of the UI and logic in each individual page, maintaining a reusable foundation in Shared. If a need arises to reuse business logic across several pages, you can move it to a layer below.
+
+Another addition to Feature-Sliced Design is the standardization of cross-imports between entities with the `@x`-notation.
+
+## How to migrate {#how-to-migrate}
+
+There are no breaking changes in v2.1, which means that a project written with FSD v2.0 is also a valid project in FSD v2.1. However, we believe that the new mental model is more beneficial for teams and especially onboarding new developers, so we recommend making minor adjustments to your decomposition.
+
+### Merge slices
+
+A simple way to start is by running our linter, [Steiger][steiger], on the project. Steiger is built with the new mental model, and the most helpful rules will be:
+
+- [`insignificant-slice`][insignificant-slice] — if an entity or feature is only used in one page, this rule will suggest merging that entity or feature into the page entirely.
+- [`excessive-slicing`][excessive-slicing] — if a layer has too many slices, it's usually a sign that the decomposition is too fine-grained. This rule will suggest merging or grouping some slices to help project navigation.
+
+```bash
+npx steiger src
+```
+
+This will help you identify which slices are only used once, so that you could reconsider if they are really necessary. In such considerations, keep in mind that a layer forms some kind of global namespace for all the slices inside of it. Just as you wouldn't pollute the global namespace with variables that are only used once, you should treat a place in the namespace of a layer as valuable, to be used sparingly.
+
+### Standardize cross-imports
+
+If you had cross-imports between in your project before (we don't judge!), you may now take advantage of a new notation for cross-importing in Feature-Sliced Design — the `@x`-notation. It looks like this:
+
+```ts title="entities/B/some/file.ts"
+import type { EntityA } from "entities/A/@x/B";
+```
+
+For more details, check out the [Public API for cross-imports][public-api-for-cross-imports] section in the reference.
+
+[insignificant-slice]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice
+[steiger]: https://github.com/feature-sliced/steiger
+[excessive-slicing]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/excessive-slicing
+[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx
index 08855b6869..929bdcaf51 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx
+++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-nuxtjs.mdx
@@ -66,7 +66,7 @@ Thus, the file structure will look like this:
│ │ │ │ ├── home-page.vue
│ │ │ ├── index.ts
```
-Finally, let's add a root to the config:
+Finally, let's add a route to the config:
```ts title="app/router.config.ts"
import type { RouterConfig } from '@nuxt/schema'
@@ -111,8 +111,8 @@ Now, you can create routes for pages within `app` and connect pages from `pages`
For example, to add a `Home` page to your project, you need to do the following steps:
- Add a page slice inside the `pages` layer
-- Add the corresponding root inside the `app` layer
-- Align the page from the slice with the root
+- Add the corresponding route inside the `app` layer
+- Connect the page from the slice with the route
To create a page slice, let's use the [CLI](https://github.com/feature-sliced/cli):
@@ -126,7 +126,7 @@ Create a ``home-page.vue`` file inside the ui segment, access it using the Publi
export { default as HomePage } from './ui/home-page';
```
-Create a root for this page inside the `app` layer:
+Create a route for this page inside the `app` layer:
```sh
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx
index 1bb64f57f3..88931cfa3f 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx
+++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx
@@ -34,8 +34,7 @@ the purest division will be by entity. In this case, we suggest using the follow
```
If there are connections between the entities (for example, the Country entity has a field-list of City entities),
-then you can use an [experimental approach to organized cross-imports
-via @x-notation](https://github.com/feature-sliced/documentation/discussions/390#discussioncomment-5570073) or consider the alternative solution below.
+then you can use the [public API for cross-imports][public-api-for-cross-imports] or consider the alternative solution below.
### Alternative solution — keep it in shared
@@ -433,3 +432,5 @@ export const apiClient = new ApiClient(API_URL);
- [(GitHub) Sample Project](https://github.com/ruslan4432013/fsd-react-query-example)
- [(CodeSandbox) Sample Project](https://codesandbox.io/p/github/ruslan4432013/fsd-react-query-example/main)
- [About the query factory](https://tkdodo.eu/blog/the-query-options-api)
+
+[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports
diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx
index 08c81813da..886b2381ee 100644
--- a/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx
+++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx
@@ -44,10 +44,10 @@ Thus, your file structure should look like this:
│ ├── app
│ │ ├── index.html
│ │ ├── routes
-│ ├── pages # Папка pages, закреплённая за FSD
+│ ├── pages # FSD Pages folder
```
-Now, you can create roots for pages within `app` and connect pages from `pages` to them.
+Now, you can create routes for pages within `app` and connect pages from `pages` to them.
For example, to add a home page to your project, you need to do the following steps:
- Add a page slice inside the `pages` layer
@@ -60,13 +60,13 @@ To create a page slice, let's use the [CLI](https://github.com/feature-sliced/cl
fsd pages home
```
-Create a ``home-page.vue`` file inside the ui segment, access it using the Public API
+Create a ``home-page.svelte`` file inside the ui segment, access it using the Public API
```ts title="src/pages/home/index.ts"
-export { default as HomePage } from './ui/home-page';
+export { default as HomePage } from './ui/home-page.svelte';
```
-Create a root for this page inside the `app` layer:
+Create a route for this page inside the `app` layer:
```sh
@@ -82,7 +82,7 @@ Create a root for this page inside the `app` layer:
│ │ │ ├── index.ts
```
-Add your page component inside the `index.svelte` file:
+Add your page component inside the `+page.svelte` file:
```html title="src/app/routes/+page.svelte"
+```
+
+## 環境宣言ファイル(`*.d.ts`)
+
+一部のパッケージ、例えば[Vite][ext-vite]や[ts-reset][ext-ts-reset]は、アプリケーションで動作するために環境宣言ファイルを必要とします。通常、これらは小さくて簡単なので、特にアーキテクチャを必要とせず、単に`src/`に置くことができます。`src`をより整理されたものにするために、App層の`app/ambient/`に保存することもできます。
+
+他のパッケージは単に型を持たず、その型を未定義として宣言する必要があるか、あるいは自分で型を作成する必要があるかもしれません。これらの型の良い場所は`shared/lib`で、`shared/lib/untyped-packages`のようなフォルダーです。そこに`%LIBRARY_NAME%.d.ts`というファイルを作成し、必要な型を宣言します。
+
+```ts title="shared/lib/untyped-packages/use-react-screenshot.d.ts"
+// このライブラリには型がなく、自分で型を書くのは億劫です。
+declare module "use-react-screenshot";
+```
+
+## 型の自動生成
+
+外部ソースから型を生成することは、しばしば便利です。例えば、OpenAPIスキーマからバックエンドの型を生成することができます。この場合、これらの型のためにコード内に特別な場所を作成します。例えば、`shared/api/openapi`のようにします。これらのファイルが何であるか、どのように再生成されるかを説明するREADMEをこのフォルダーに含めておくと理想的です。
+
+[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers
+[ext-type-fest]: https://github.com/sindresorhus/type-fest
+[ext-zod]: https://zod.dev
+[ext-vite]: https://vitejs.dev
+[ext-ts-reset]: https://www.totaltypescript.com/ts-reset
diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx
new file mode 100644
index 0000000000..b536ecddbc
--- /dev/null
+++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/examples/white-labels.mdx
@@ -0,0 +1,11 @@
+---
+sidebar_position: 8
+sidebar_class_name: sidebar-item--wip
+unlisted: true
+---
+
+import WIP from '@site/src/shared/ui/wip/tmpl.mdx'
+
+# ホワイトラベル
+
+
diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/guides/index.mdx
new file mode 100644
index 0000000000..f6b7376683
--- /dev/null
+++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/index.mdx
@@ -0,0 +1,46 @@
+---
+hide_table_of_contents: true
+pagination_prev: get-started/index
+---
+
+# 🎯 ガイド
+
+実践指向
+
+
+
+## Main
+
+import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx"
+import { StarOutlined, SearchOutlined, TeamOutlined, VerifiedOutlined } from "@ant-design/icons";
+
+
+
+
+
+
diff --git a/i18n/kr/docusaurus-plugin-content-docs/community/team.mdx b/i18n/kr/docusaurus-plugin-content-docs/community/team.mdx
new file mode 100644
index 0000000000..24ebc84ba9
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/community/team.mdx
@@ -0,0 +1,18 @@
+---
+sidebar_class_name: sidebar-item--wip
+sidebar_position: 2
+---
+
+import WIP from '@site/src/shared/ui/wip/tmpl.mdx'
+
+# 팀 소개
+
+
+
+## 코어 팀
+
+### 챔피언
+
+## 기여자
+
+## 협력 기업
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/about/mission.md b/i18n/kr/docusaurus-plugin-content-docs/current/about/mission.md
new file mode 100644
index 0000000000..a13527ccc6
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/about/mission.md
@@ -0,0 +1,51 @@
+---
+sidebar_position: 1
+---
+
+# 미션
+
+이 문서에서는 방법론을 개발할 때 우리가 추구하는 목표와 적용 가능성의 한계를 설명합니다.
+
+- 방법론 개발의 목표는 이념과 단순성 간의 균형을 맞추는 것입니다.
+- 모든 사람에게 완벽하게 맞는 만능 해결책을 만들 수는 없습니다.
+
+**그럼에도, 방법론은 다양한 개발자들에게 접근하기 쉽고 실용적이어야 합니다.**
+
+## 목표
+
+### 다양한 개발자에게 직관적이고 명확하게
+
+방법론은 프로젝트에 참여하는 대부분의 팀원들이 쉽게 접근하고 이해할 수 있도록 설계되어야 합니다.
+
+*향후 어떤 도구가 추가되더라도, 시니어나 리더 개발자들만 이해할 수 있는 방법론이라면 충분하지 않습니다.*
+
+### 일상적인 문제 해결
+
+방법론은 개발 프로젝트에서 일상적으로 발생하는 문제에 대해 명확한 이유와 해결책을 제시해야 합니다.
+
+**이를 위해 CLI와 린터(linter) 같은 도구들도 함께 제공해야 합니다.**
+
+이를 통해 개발자들은 아키텍처와 개발상의 오랜 문제를 우회할 수 있는 검증된 접근 방식을 활용할 수 있습니다.
+
+> *@sergeysova: 방법론을 기반으로 코드를 작성하는 개발자는 이미 많은 문제에 대한 해결책이 마련되어 있기 때문에, 문제 발생 빈도가 10배 정도 줄어들 것이라고 상상해보세요.*
+
+## 한계
+
+우리는 *특정 관점을 강요하고* 싶지 않으며, 개발자로서의 *여러 습관이 문제 해결을 방해할 수 있다는 점도 이해합니다.*
+
+모든 개발자의 시스템을 설계하거나 개발하는 데 경험 수준이 다르기 떄문에, **다음 사항을 이해하는 것이 중요합니다:**
+
+- **모두에게 동일하게 적용되지 않을 수 있음:**: 너무 간단하거나 명확한 접근법이 모든 상황에서 항상 효과적이지는 않습니다.
+ > *@sergeysova: 어떤 개념들은 문제를 직접 겪고, 오랜 시간을 들여 해결하는 과정을 통해서만 직관적으로 이해할 수 있는 경우가 많습니다.
+ >
+ > - *수학: 그래프 이론.*
+ > - *물리학: 양자 역학.*
+ > - *프로그래밍: 애플리케이션 아키텍처.*
+
+- **가능하고 바람직한 방향**: 단순함과 확장 가능성의 조화
+
+## 참고 자료
+
+- [아키텍쳐 문제들][refs-architecture--problems]
+
+[refs-architecture--problems]: /docs/about/understanding/architecture#problems
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md
new file mode 100644
index 0000000000..4b1d8468f4
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md
@@ -0,0 +1,69 @@
+---
+sidebar_position: 20
+pagination_next: guides/index
+---
+
+# FAQ
+
+:::info
+
+여러분은 [Telegram chat][telegram], [Discord community][discord] 그리고 [GitHub Discussions][github-discussions]에서 질문을 할 수 있습니다.
+
+:::
+
+### toolkit이나 linter가 있나요?
+
+네! 우리는 CLI 또는 IDE를 통해 프로젝트의 아키텍처와 [폴더 생성기][ext-tools]를 확인하기 위한 [Steiger][ext-steiger]라는 linter를 가지고 있습니다.
+
+### Where to store the layout/template of pages?
+
+순수한 마크업 레이아웃이 필요하다면 `shared/ui`에 보관할 수 있습니다. 상위 계층을 사용해야 한다면 몇 가지 옵션이 있습니다.
+
+- 레이아웃이 필요 없을 수도 있습니다. 레이아웃이 몇 줄밖에 안 된다면, 추상화하려고 하기보다는 각 페이지에서 코드를 중복하는 것이 합리적일 수 있습니다.
+- 레이아웃이 필요하다면, 별도의 위젯이나 페이지로 만들고 App의 라우터 설정에서 조합할 수 있습니다. 중첩 라우팅도 다른 옵션입니다.
+
+### feature와 entity의 차이점이 무엇인가요?
+
+*entity*는 앱이 다루는 실제 개념입니다. *feature*는 앱 사용자에게 실제 가치를 제공하는 상호작용, 즉 사람들이 entity로 하고 싶어하는 것입니다.
+
+더 자세한 정보와 예시는 [slices][reference-entities] 참조 페이지를 확인하세요.
+
+### pages/features/entities를 서로 포함시킬 수 있나요?
+
+네, 하지만 이런 포함은 상위 계층에서 이루어져야 합니다. 예를 들어, 위젯 내부에서 여러 기능을 가져와서 하나의 기능을 다른 기능의 props/children으로 삽입할 수 있습니다.
+
+한 기능을 다른 기능에서 가져올 수는 없습니다. 이는 [**계층에 대한 가져오기 규칙**][import-rule-layers]에 의해 금지됩니다.
+
+### 아토믹 디자인은 어떤가요?
+
+현재 버전의 방법론은 Feature-Sliced Design과 함께 아토믹 디자인을 사용하는 것을 요구하지도, 금지하지도 않습니다.
+
+예를 들어, 아토믹 디자인은 모듈의 `ui` 세그먼트에 [잘 적용될 수 있습니다](https://t.me/feature_sliced/1653).
+
+### FSD에 대한 유용한 리소스/기사 등이 있나요?
+
+네! https://github.com/feature-sliced/awesome 를 참조하세요.
+
+### Feature-Sliced Design이 왜 필요한가요?
+
+프로젝트를 주요 가치 창출 구성 요소 측면에서 빠르게 개요를 파악하는 데 도움이 됩니다. 표준화된 아키텍처는 온보딩 속도를 높이고 코드 구조에 대한 논쟁을 해결합니다. FSD가 만들어진 이유에 대해 더 자세히 알아보려면 [동기][motivation] 페이지를 참조하세요.
+
+### 초보 개발자에게 아키텍처/방법론이 필요한가요?
+
+그렇다고 볼 수 있습니다.
+
+*보통 한 사람이 프로젝트를 설계하고 개발할 때는 모든 것이 순조롭게 진행됩니다. 하지만 개발에 중단이 있거나 새로운 개발자가 팀에 합류하면 문제가 발생합니다*
+
+
+### 인증 컨텍스트는 어떻게 다루나요?
+
+[여기](/docs/guides/examples/auth)에서 답변했습니다.
+
+[ext-steiger]: https://github.com/feature-sliced/steiger
+[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools
+[import-rule-layers]: /docs/reference/layers#import-rule-on-layers
+[reference-entities]: /docs/reference/layers#entities
+[motivation]: /docs/about/motivation
+[telegram]: https://t.me/feature_sliced
+[discord]: https://discord.gg/S8MzWTUsmp
+[github-discussions]: https://github.com/feature-sliced/documentation/discussions
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/overview.mdx
index 75d7f69a32..23967ce248 100644
--- a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/overview.mdx
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/overview.mdx
@@ -65,15 +65,15 @@ FSD는 규모에 관계 없이 모든 팀과 프로젝트에서 사용할 수
레이어는 모든 FSD 프로젝트에서 표준화되어 있습니다. 모든 레이어를 사용할 필요는 없지만, 이름은 중요합니다. 현재(위에서 아래로) 7개가 있습니다:
-1. App\* - 앱을 실행하는 모든 것 - 라우팅, 진입점, 전역 스타일, 프로바이더.
-2. Processes(더 이상 사용되지 않음) - 페이지 간 복잡한 시나리오.
-3. Pages - 전체 페이지 또는 중첩 라우팅에서 페이지의 주요 부분.
-4. Widgets - 독립적으로 작동하는 대규모 기능 또는 UI 컴포넌트, 보통 하나의 완전한 기능.
-5. Features - 제품 전반에 걸쳐 재사용되는 기능 구현체로, 사용자에게 실질적인 비즈니스 가치를 제공하는 동작.
-6. Entities - 프로젝트가 다루는 비즈니스 엔티티, 예를 들어 user 또는 product.
-7. Shared* - 재사용 가능한 기능, 특히 프로젝트/비즈니스의 특성과 분리되어 있을 때 (반드시 그럴 필요는 없음).
+1. **App\*** - 앱을 실행하는 모든 것 - 라우팅, 진입점, 전역 스타일, 프로바이더.
+2. **Processes**(더 이상 사용되지 않음) - 페이지 간 복잡한 시나리오.
+3. **Pages** - 전체 페이지 또는 중첩 라우팅에서 페이지의 주요 부분.
+4. **Widgets** - 독립적으로 작동하는 대규모 기능 또는 UI 컴포넌트, 보통 하나의 완전한 기능.
+5. **Features** - 제품 전반에 걸쳐 재사용되는 기능 구현체로, 사용자에게 실질적인 비즈니스 가치를 제공하는 동작.
+6. **Entities** - 프로젝트가 다루는 비즈니스 엔티티, 예를 들어 user 또는 product.
+7. **Shared*** - 재사용 가능한 기능, 특히 프로젝트/비즈니스의 특성과 분리되어 있을 때 (반드시 그럴 필요는 없음).
-_\* - App과 Shared는 다른 레이어들과 달리 슬라이스를 가지지 않으며, 직접 세그먼트로 구성됩니다._
+_\* - **App**과 **Shared**는 다른 레이어들과 달리 슬라이스를 가지지 않으며, 직접 세그먼트로 구성됩니다._
레이어를 다룰 때의 중요한 점은 한 레이어의 구성 요소는 반드시 아래에 있는 레이어의 구성 요소만 알수있고 임포트할 수 있다는 것입니다.
@@ -130,7 +130,7 @@ FSD로 마이그레이션하고자 하는 기존 코드베이스가 있다면,
[tutorial]: /docs/get-started/tutorial
[examples]: /examples
-[migration]: /docs/guides/migration/from-legacy
+[migration]: /docs/guides/migration/from-custom
[ext-steiger]: https://github.com/feature-sliced/steiger
[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools
[ext-telegram]: https://t.me/feature_sliced
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/tutorial.md
new file mode 100644
index 0000000000..ba8c07b495
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/tutorial.md
@@ -0,0 +1,2270 @@
+---
+sidebar_position: 2
+---
+# 튜토리얼
+
+## Part 1. 설계
+
+이 튜토리얼에서는 Real World App이라고도 알려진 Conduit를 살펴보겠습니다. Conduit는 기본적인 [Medium](https://medium.com/) 클론입니다 - 글을 읽고 쓸 수 있으며 다른 사람의 글에 댓글을 달 수 있습니다.
+
+![Conduit home page](/img/tutorial/realworld-feed-anonymous.jpg)
+
+이 애플리케이션은 매우 작은 애플리케이션이므로 과도한 분해를 피하고 간단하게 유지할 것입니다. 전체 애플리케이션이 세 개의 레이어인 **App**, **Pages**, 그리고 **Shared**에 맞춰 들어갈 것입니다. 그렇지 않다면 우리는 계속해서 추가적인 레이어를 도입할 것입니다. 준비되셨나요?
+
+### 먼저 페이지를 나열해 봅시다.
+
+위의 스크린샷을 보면 최소한 다음과 같은 페이지들이 있다고 가정할 수 있습니다:
+
+- 홈 (글 피드)
+- 로그인 및 회원가입
+- 글 읽기
+- 글 편집기
+- 사용자 프로필 보기
+- 사용자 프로필 편집 (사용자 설정)
+
+이 페이지들 각각은 Pages *레이어*의 독립된 *슬라이스*가 될 것입니다. 개요에서 언급했듯이 슬라이스는 단순히 레이어 내의 폴더이고, 레이어는 `pages`와 같은 미리 정의된 이름을 가진 폴더일 뿐입니다.
+
+따라서 우리의 Pages 폴더는 다음과 같이 보일 것입니다.
+
+```
+📂 pages/
+ 📁 feed/
+ 📁 sign-in/
+ 📁 article-read/
+ 📁 article-edit/
+ 📁 profile/
+ 📁 settings/
+```
+
+Feature-Sliced Design이 규제되지 않은 코드 구조와 다른 주요 차이점은 페이지들이 서로를 참조할 수 없다는 것입니다. 즉, 한 페이지가 다른 페이지의 코드를 가져올 수 없습니다. 이는 **레이어의 import 규칙** 때문입니다.
+
+*슬라이스의 모듈은 엄격히 아래에 있는 레이어에 위치한 다른 슬라이스만 가져올 수 있습니다.*
+
+이 경우 페이지는 슬라이스이므로, 이 페이지 내의 모듈(파일)은 같은 레이어인 Pages가 아닌 아래 레이어의 코드만 참조할 수 있습니다.
+
+### 피드 자세히 보기
+
+
+
+
+
+피드 페이지에는 세 가지 동적 영역이 있습니다.
+
+1. 로그인 여부를 나타내는 로그인 링크
+2. 피드에서 필터링을 트리거하는 태그 목록
+3. 좋아요 버튼이 있는 하나/두 개의 글 피드
+
+로그인 링크는 모든 페이지에 공통적인 헤더의 일부이므로 나중에 따로 다루겠습니다.
+
+#### 태그 목록
+
+태그 목록을 만들기 위해서는 사용 가능한 태그를 가져오고, 각 태그를 칩으로 렌더링하고, 선택된 태그를 클라이언트 측 저장소에 저장해야 합니다. 이러한 작업들은 각각 "API 상호작용", "사용자 인터페이스", "저장소" 카테고리에 속합니다. Feature-Sliced Design에서는 코드를 *세그먼트*를 사용하여 목적별로 분리합니다. 세그먼트는 슬라이스 내의 폴더이며, 목적을 설명하는 임의의 이름을 가질 수 있지만, 일부 목적은 너무 일반적이어서 특정 세그먼트 이름에 대한 규칙이 있습니다.
+
+
+- 📂 `api/` 백엔드 상호작용
+- 📂 `ui/` 렌더링과 외관을 다루는 코드
+- 📂 `model/` 저장소와 비즈니스 로직
+- 📂 `config/` 기능 플래그, 환경 변수 및 기타 구성 형식
+
+태그를 가져오는 코드는 `api`에, 태그 컴포넌트는 `ui`에, 저장소 상호작용은 `model`에 배치할 것입니다.
+
+#### 글
+
+같은 그룹화 원칙을 사용하여 글 피드를 같은 세 개의 세그먼트로 분해할 수 있습니다.
+
+- 📂 `api/`: 좋아요 수가 포함된 페이지네이션된 글 가져오기
+- 📂 `ui/`:
+ - 태그가 선택된 경우 추가 탭을 렌더링할 수 있는 탭 목록
+ - 개별 글
+ - 기능적 페이지네이션
+- 📂 `model/`: 현재 로드된 글과 현재 페이지의 클라이언트 측 저장소 (필요한 경우)
+
+### 일반적인 코드 재사용
+
+대부분의 페이지는 의도가 매우 다르지만, 앱 전체에 걸쳐 일부 요소는 동일하게 유지됩니다. 예를 들어, 디자인 언어를 준수하는 UI 키트나 모든 것이 동일한 인증 방식으로 REST API를 통해 수행되는 백엔드의 규칙 등이 있습니다. 슬라이스는 격리되도록 설계되었기 때문에, 코드 재사용은 더 낮은 계층인 **Shared**에 의해 촉진됩니다.
+
+
+Shared는 슬라이스가 아닌 세그먼트를 포함한다는 점에서 다른 계층과 다릅니다. 이런 면에서 Shared 계층은 계층과 슬라이스의 하이브리드로 생각할 수 있습니다.
+
+일반적으로 Shared의 코드는 미리 계획되지 않고 개발 중에 추출됩니다. 실제로 어떤 코드 부분이 공유되는지는 개발 중에만 명확해지기 때문입니다. 그러나 어떤 종류의 코드가 자연스럽게 Shared에 속하는지 머릿속에 메모해 두는 것은 여전히 도움이 됩니다.
+
+
+- 📂 `ui/` — UI 키트, 비즈니스 로직이 없는 순수한 UI. 예: 버튼, 모달 대화 상자, 폼 입력.
+- 📂 `api/` — 요청 생성 기본 요소(예: 웹의 `fetch()`)에 대한 편의 래퍼 및 선택적으로 백엔드 사양에 따라 특정 요청을 트리거하는 함수.
+- 📂 `config/` — 환경 변수 파싱
+- 📂 `i18n/` — 언어 지원에 대한 구성
+- 📂 `router/` — 라우팅 기본 요소 및 라우트 상수
+
+이는 Shared의 세그먼트 이름의 몇 가지 예시일 뿐이며, 이 중 일부를 생략하거나 자신만의 세그먼트를 만들 수 있습니다. 새로운 세그먼트를 만들 때 기억해야 할 유일한 중요한 점은 세그먼트 이름이 **본질(무엇인지)이 아닌 목적(왜)을 설명해야 한다**는 것입니다. "components", "hooks", "modals"과 같은 이름은 이 파일들이 무엇인지는 설명하지만 내부 코드를 탐색하는 데 도움이 되지 않기 때문에 사용해서는 안 됩니다. 이는 팀원들이 이러한 폴더의 모든 파일을 파헤쳐야 하며, 관련 없는 코드를 가까이 유지하게 되어 리팩토링의 영향을 받는 코드 영역이 넓어지고 결과적으로 코드 리뷰와 테스트를 더 어렵게 만듭니다.
+
+### 엄격한 공개 API 정의
+
+Feature-Sliced Design의 맥락에서 *공개 API*라는 용어는 슬라이스나 세그먼트가 프로젝트의 다른 모듈에서 가져올 수 있는 것을 선언하는 것을 의미합니다. 예를 들어, JavaScript에서는 슬라이스의 다른 파일에서 객체를 다시 내보내는 `index.js` 파일일 수 있습니다. 이를 통해 외부 세계와의 계약(즉, 공개 API)이 동일하게 유지되는 한 슬라이스 내부의 코드를 자유롭게 리팩토링할 수 있습니다.
+
+슬라이스가 없는 Shared 계층의 경우, Shared의 모든 것에 대한 단일 인덱스를 정의하는 것과 반대로 각 세그먼트에 대해 별도의 공개 API를 정의하는 것이 일반적으로 더 편리합니다. 이렇게 하면 Shared에서의 가져오기가 자연스럽게 의도별로 구성됩니다. 슬라이스가 있는 다른 계층의 경우 반대가 사실입니다 — 일반적으로 슬라이스당 하나의 인덱스를 정의하고 슬라이스가 외부 세계에 알려지지 않은 자체 세그먼트 세트를 결정하도록 하는 것이 더 실용적입니다. 다른 계층은 일반적으로 내보내기가 훨씬 적기 때문입니다.
+
+우리의 슬라이스/세그먼트는 서로에게 다음과 같이 나타날 것입니다.
+
+```
+📂 pages/
+ 📂 feed/
+ 📄 index
+ 📂 sign-in/
+ 📄 index
+ 📂 article-read/
+ 📄 index
+ 📁 …
+📂 shared/
+ 📂 ui/
+ 📄 index
+ 📂 api/
+ 📄 index
+ 📁 …
+```
+
+`pages/feed`나 `shared/ui`와 같은 폴더 내부의 내용은 해당 폴더에만 알려져 있으며, 다른 파일은 이러한 폴더의 내부 구조에 의존해서는 안 됩니다.
+
+
+### UI의 큰 재사용 블록
+
+앞서 모든 페이지에 나타나는 헤더를 다시 살펴보기로 했습니다. 모든 페이지에서 처음부터 다시 만드는 것은 비실용적이므로 재사용하고 싶을 것입니다. 우리는 이미 코드 재사용을 용이하게 하는 Shared를 가지고 있지만, Shared에 큰 UI 블록을 넣는 데는 주의할 점이 있습니다 — Shared 계층은 위의 계층에 대해 알지 못해야 합니다.
+
+Shared와 Pages 사이에는 Entities, Features, Widgets의 세 가지 다른 계층이 있습니다. 일부 프로젝트는 이러한 계층에 큰 재사용 가능한 블록에 필요한 것이 있을 수 있으며, 이는 해당 재사용 가능한 블록을 Shared에 넣을 수 없다는 것을 의미합니다. 그렇지 않으면 상위 계층에서 가져오게 되어 금지됩니다. 이것이 Widgets 계층이 필요한 이유입니다. Widgets는 Shared, Entities, Features 위에 위치하므로 이들 모두를 사용할 수 있습니다.
+
+우리의 경우, 헤더는 매우 간단합니다 — 정적 로고와 최상위 탐색입니다. 탐색은 사용자가 현재 로그인했는지 여부를 확인하기 위해 API에 요청을 해야 하지만, 이는 `api` 세그먼트에서 간단한 가져오기로 처리할 수 있습니다. 따라서 우리는 헤더를 Shared에 유지할 것입니다.
+
+### 폼이 있는 페이지 자세히 보기
+
+읽기가 아닌 편집을 위한 페이지도 살펴보겠습니다.
+
+![Conduit post editor](/img/tutorial/realworld-editor-authenticated.jpg)
+
+간단해 보이지만, 폼 유효성 검사, 오류 상태, 데이터 지속성 등 아직 탐구하지 않은 애플리케이션 개발의 여러 측면을 포함하고 있습니다.
+
+이 페이지를 만들려면 Shared에서 일부 입력과 버튼을 가져와 이 페이지의 `ui` 세그먼트에서 폼을 구성할 것입니다. 그런 다음 `api` 세그먼트에서 백엔드에 글을 생성하는 변경 요청을 정의할 것입니다.
+
+요청을 보내기 전에 유효성을 검사하려면 유효성 검사 스키마가 필요하며, 이를 위한 좋은 위치는 데이터 모델이기 때문에 `model` 세그먼트입니다. 여기서 오류 메시지를 생성하고 `ui` 세그먼트의 다른 컴포넌트를 사용하여 표시할 것입니다.
+
+사용자 경험을 개선하기 위해 우발적인 데이터 손실을 방지하기 위해 입력을 지속시킬 수도 있습니다. 이것도 `model` 세그먼트의 작업입니다.
+
+### 요약
+
+우리는 여러 페이지를 검토하고 애플리케이션의 예비 구조를 개략적으로 설명했습니다.
+
+1. Shared layer
+ 1. `ui`는 재사용 가능한 UI 키트를 포함할 것입니다.
+ 2. `api`는 백엔드와의 기본적인 상호작용을 포함할 것입니다.
+ 3. 나머지는 필요에 따라 정리될 것입니다.
+2. Pages layer — 각 페이지는 별도의 슬라이스입니다.
+ 1. `ui`는 페이지 자체와 모든 부분을 포함할 것입니다.
+ 2. `api`는 `shared/api`를 사용하여 더 특화된 데이터 가져오기를 포함할 것입니다.
+ 3. `model`은 표시할 데이터의 클라이언트 측 저장소를 포함할 수 있습니다.
+
+이제 코드 작성을 시작해 봅시다!
+
+## Part 2. 코드 작성
+
+이제 설계를 완료했으니 실제로 코드를 작성해 봅시다. React와 [Remix](https://remix.run)를 사용할 것입니다.
+
+이 프로젝트를 위한 템플릿이 준비되어 있습니다. GitHub에서 클론하여 시작하세요. [https://github.com/feature-sliced/tutorial-conduit/tree/clean](https://github.com/feature-sliced/tutorial-conduit/tree/clean).
+
+`npm install`로 의존성을 설치하고 `npm run dev`로 개발 서버를 시작하세요. [http://localhost:3000](http://localhost:3000)을 열면 빈 앱이 보일 것입니다.
+
+
+### 페이지 레이아웃
+
+모든 페이지에 대한 빈 컴포넌트를 만드는 것부터 시작하겠습니다. 프로젝트에서 다음 명령을 실행하세요.
+
+```bash
+npx fsd pages feed sign-in article-read article-edit profile settings --segments ui
+```
+
+이렇게 하면 `pages/feed/ui/`와 같은 폴더와 모든 페이지에 대한 인덱스 파일인 `pages/feed/index.ts`가 생성됩니다.
+
+### 피드 페이지 연결
+
+애플리케이션의 루트 경로를 피드 페이지에 연결해 봅시다. `pages/feed/ui`에 `FeedPage.tsx` 컴포넌트를 만들고 다음 내용을 넣으세요:
+
+```tsx title="pages/feed/ui/FeedPage.tsx"
+export function FeedPage() {
+ return (
+
+
+
+
conduit
+
A place to share your knowledge.
+
+
+
+ );
+}
+```
+
+그런 다음 피드 페이지의 공개 API인 `pages/feed/index.ts` 파일에서 이 컴포넌트를 다시 내보내세요.
+
+```ts title="pages/feed/index.ts"
+export { FeedPage } from "./ui/FeedPage";
+```
+
+이제 루트 경로에 연결합니다. Remix에서 라우팅은 파일 기반이며, 라우트 파일은 `app/routes` 폴더에 있어 Feature-Sliced Design과 잘 맞습니다.
+
+`app/routes/_index.tsx`에서 `FeedPage` 컴포넌트를 사용하세요.
+
+```tsx title="app/routes/_index.tsx"
+import type { MetaFunction } from "@remix-run/node";
+import { FeedPage } from "pages/feed";
+
+export const meta: MetaFunction = () => {
+ return [{ title: "Conduit" }];
+};
+
+export default FeedPage;
+```
+
+그런 다음 개발 서버를 실행하고 애플리케이션을 열면 Conduit 배너가 보일 것입니다!
+
+![The banner of Conduit](/img/tutorial/conduit-banner.jpg)
+
+### API 클라이언트
+
+RealWorld 백엔드와 통신하기 위해 Shared에 편리한 API 클라이언트를 만들어 봅시다. 클라이언트를 위한 `api`와 백엔드 기본 URL과 같은 변수를 위한 `config`, 두 개의 세그먼트를 만드세요.
+
+
+```bash
+npx fsd shared --segments api config
+```
+
+그런 다음 `shared/config/backend.ts`를 만드세요.
+
+```tsx title="shared/config/backend.ts"
+export const backendBaseUrl = "https://api.realworld.io/api";
+```
+
+```tsx title="shared/config/index.ts"
+export { backendBaseUrl } from "./backend";
+```
+
+RealWorld 프로젝트는 편리하게 [OpenAPI 사양](https://github.com/gothinkster/realworld/blob/main/api/openapi.yml)을 제공하므로, 클라이언트를 위한 자동 생성 타입을 활용할 수 있습니다. 추가 타입 생성기가 포함된 [`openapi-fetch` 패키지](https://openapi-ts.pages.dev/openapi-fetch/)를 사용할 것입니다.
+
+다음 명령을 실행하여 최신 API 타입을 생성하세요.
+
+```bash
+npm run generate-api-types
+```
+
+이렇게 하면 `shared/api/v1.d.ts` 파일이 생성됩니다. 이 파일을 사용하여 `shared/api/client.ts`에 타입이 지정된 API 클라이언트를 만들 것입니다.
+
+```tsx title="shared/api/client.ts"
+import createClient from "openapi-fetch";
+
+import { backendBaseUrl } from "shared/config";
+import type { paths } from "./v1";
+
+export const { GET, POST, PUT, DELETE } = createClient({ baseUrl: backendBaseUrl });
+```
+
+```tsx title="shared/api/index.ts"
+export { GET, POST, PUT, DELETE } from "./client";
+```
+
+### 피드의 실제 데이터
+
+이제 백엔드에서 가져온 글을 피드에 추가할 수 있습니다. 글 미리보기 컴포넌트를 구현하는 것부터 시작하겠습니다.
+
+다음 내용으로 `pages/feed/ui/ArticlePreview.tsx`를 만드세요.
+
+```tsx title="pages/feed/ui/ArticlePreview.tsx"
+export function ArticlePreview({ article }) { /* TODO */ }
+```
+
+TypeScript를 사용하고 있으므로 글 객체에 타입을 지정하면 좋을 것 같습니다. 생성된 `v1.d.ts`를 살펴보면 글 객체가 `components["schemas"]["Article"]`을 통해 사용 가능한 것을 볼 수 있습니다. 그럼 Shared에 데이터 모델이 있는 파일을 만들고 모델을 내보내겠습니다.
+
+```tsx title="shared/api/models.ts"
+import type { components } from "./v1";
+
+export type Article = components["schemas"]["Article"];
+```
+
+```tsx title="shared/api/index.ts"
+export { GET, POST, PUT, DELETE } from "./client";
+
+export type { Article } from "./models";
+```
+
+이제 글 미리보기 컴포넌트로 돌아가 데이터로 마크업을 채울 수 있습니다. 컴포넌트를 다음 내용으로 업데이트하세요.
+
+```tsx title="pages/feed/ui/ArticlePreview.tsx"
+import { Link } from "@remix-run/react";
+import type { Article } from "shared/api";
+
+interface ArticlePreviewProps {
+ article: Article;
+}
+
+export function ArticlePreview({ article }: ArticlePreviewProps) {
+ return (
+
+ );
+}
+```
+
+좋아요 버튼은 지금은 아무 작업도 하지 않습니다. 글 읽기 페이지를 만들고 좋아요 기능을 구현할 때 수정하겠습니다.
+
+이제 글을 가져와서 이러한 카드를 여러 개 렌더링할 수 있습니다. Remix에서 데이터 가져오기는 *로더* — 페이지가 필요로 하는 것을 정확히 가져오는 서버 측 함수 — 를 통해 수행됩니다. 로더는 페이지를 대신하여 API와 상호 작용하므로 페이지의 `api` 세그먼트에 넣을 것입니다:
+
+```tsx title="pages/feed/api/loader.ts"
+import { json } from "@remix-run/node";
+
+import { GET } from "shared/api";
+
+export const loader = async () => {
+ const { data: articles, error, response } = await GET("/articles");
+
+ if (error !== undefined) {
+ throw json(error, { status: response.status });
+ }
+
+ return json({ articles });
+};
+```
+
+페이지에 연결하려면 라우트 파일에서 `loader`라는 이름으로 내보내야 합니다.
+
+```tsx title="pages/feed/index.ts"
+export { FeedPage } from "./ui/FeedPage";
+export { loader } from "./api/loader";
+```
+
+```tsx title="app/routes/_index.tsx"
+import type { MetaFunction } from "@remix-run/node";
+import { FeedPage } from "pages/feed";
+
+export { loader } from "pages/feed";
+
+export const meta: MetaFunction = () => {
+ return [{ title: "Conduit" }];
+};
+
+export default FeedPage;
+```
+
+마지막 단계는 피드에 이러한 카드를 렌더링하는 것입니다. `FeedPage`를 다음 코드로 업데이트하세요.
+
+```tsx title="pages/feed/ui/FeedPage.tsx"
+import { useLoaderData } from "@remix-run/react";
+
+import type { loader } from "../api/loader";
+import { ArticlePreview } from "./ArticlePreview";
+
+export function FeedPage() {
+ const { articles } = useLoaderData();
+
+ return (
+
+
+
+
conduit
+
A place to share your knowledge.
+
+
+
+
+
+
+ {articles.articles.map((article) => (
+
+ ))}
+
+
+
+
+ );
+}
+```
+
+### 태그로 필터링
+
+태그와 관련해서는 백엔드에서 태그를 가져오고 현재 선택된 태그를 저장해야 합니다. 가져오기 방법은 이미 알고 있습니다 — 로더에서 또 다른 요청을 하면 됩니다. `remix-utils` 패키지에서 `promiseHash`라는 편리한 함수를 사용할 것입니다. 이 패키지는 이미 설치되어 있습니다.
+
+로더 파일인 `pages/feed/api/loader.ts`를 다음 코드로 업데이트하세요.
+
+```tsx title="pages/feed/api/loader.ts"
+import { json } from "@remix-run/node";
+import type { FetchResponse } from "openapi-fetch";
+import { promiseHash } from "remix-utils/promise";
+
+import { GET } from "shared/api";
+
+async function throwAnyErrors(
+ responsePromise: Promise>,
+) {
+ const { data, error, response } = await responsePromise;
+
+ if (error !== undefined) {
+ throw json(error, { status: response.status });
+ }
+
+ return data as NonNullable;
+}
+
+export const loader = async () => {
+ return json(
+ await promiseHash({
+ articles: throwAnyErrors(GET("/articles")),
+ tags: throwAnyErrors(GET("/tags")),
+ }),
+ );
+};
+```
+
+
+오류 처리를 일반 함수 `throwAnyErrors`로 추출했다는 점에 주목하세요. 꽤 유용해 보이므로 나중에 재사용할 수 있을 것 같습니다. 지금은 그냥 주목해 두겠습니다.
+
+이제 태그 목록으로 넘어갑시다. 이는 상호작용이 가능해야 합니다 — 태그를 클릭하면 해당 태그가 선택되어야 합니다. Remix 규칙에 따라 URL 검색 매개변수를 선택된 태그의 저장소로 사용할 것입니다. 브라우저가 저장을 처리하게 하고 우리는 더 중요한 일에 집중하겠습니다.
+
+`pages/feed/ui/FeedPage.tsx`를 다음 코드로 업데이트하세요.
+
+```tsx title="pages/feed/ui/FeedPage.tsx"
+import { Form, useLoaderData } from "@remix-run/react";
+import { ExistingSearchParams } from "remix-utils/existing-search-params";
+
+import type { loader } from "../api/loader";
+import { ArticlePreview } from "./ArticlePreview";
+
+export function FeedPage() {
+ const { articles, tags } = useLoaderData();
+
+ return (
+
+
+
+
conduit
+
A place to share your knowledge.
+
+
+
+
+
+
+ {articles.articles.map((article) => (
+
+ ))}
+
+
+
+
+
Popular Tags
+
+
+
+
+
+
+
+ );
+}
+```
+
+그런 다음 로더에서 `tag` 검색 매개변수를 사용해야 합니다. `pages/feed/api/loader.ts`의 `loader` 함수를 다음과 같이 변경하세요.
+
+```tsx title="pages/feed/api/loader.ts"
+import { json, type LoaderFunctionArgs } from "@remix-run/node";
+import type { FetchResponse } from "openapi-fetch";
+import { promiseHash } from "remix-utils/promise";
+
+import { GET } from "shared/api";
+
+async function throwAnyErrors(
+ responsePromise: Promise>,
+) {
+ const { data, error, response } = await responsePromise;
+
+ if (error !== undefined) {
+ throw json(error, { status: response.status });
+ }
+
+ return data as NonNullable;
+}
+
+export const loader = async ({ request }: LoaderFunctionArgs) => {
+ const url = new URL(request.url);
+ const selectedTag = url.searchParams.get("tag") ?? undefined;
+
+ return json(
+ await promiseHash({
+ articles: throwAnyErrors(
+ GET("/articles", { params: { query: { tag: selectedTag } } }),
+ ),
+ tags: throwAnyErrors(GET("/tags")),
+ }),
+ );
+};
+```
+
+이게 전부입니다. `model` 세그먼트가 필요하지 않습니다. Remix는 꽤 깔끔하죠.
+
+### 페이지네이션
+
+비슷한 방식으로 페이지네이션을 구현할 수 있습니다. 직접 시도해 보거나 아래 코드를 복사하세요. 어차피 당신을 판단할 사람은 없습니다.
+
+```tsx title="pages/feed/api/loader.ts"
+import { json, type LoaderFunctionArgs } from "@remix-run/node";
+import type { FetchResponse } from "openapi-fetch";
+import { promiseHash } from "remix-utils/promise";
+
+import { GET } from "shared/api";
+
+async function throwAnyErrors(
+ responsePromise: Promise>,
+) {
+ const { data, error, response } = await responsePromise;
+
+ if (error !== undefined) {
+ throw json(error, { status: response.status });
+ }
+
+ return data as NonNullable;
+}
+
+/** Amount of articles on one page. */
+export const LIMIT = 20;
+
+export const loader = async ({ request }: LoaderFunctionArgs) => {
+ const url = new URL(request.url);
+ const selectedTag = url.searchParams.get("tag") ?? undefined;
+ const page = parseInt(url.searchParams.get("page") ?? "", 10);
+
+ return json(
+ await promiseHash({
+ articles: throwAnyErrors(
+ GET("/articles", {
+ params: {
+ query: {
+ tag: selectedTag,
+ limit: LIMIT,
+ offset: !Number.isNaN(page) ? page * LIMIT : undefined,
+ },
+ },
+ }),
+ ),
+ tags: throwAnyErrors(GET("/tags")),
+ }),
+ );
+};
+```
+
+```tsx title="pages/feed/ui/FeedPage.tsx"
+import { Form, useLoaderData, useSearchParams } from "@remix-run/react";
+import { ExistingSearchParams } from "remix-utils/existing-search-params";
+
+import { LIMIT, type loader } from "../api/loader";
+import { ArticlePreview } from "./ArticlePreview";
+
+export function FeedPage() {
+ const [searchParams] = useSearchParams();
+ const { articles, tags } = useLoaderData();
+ const pageAmount = Math.ceil(articles.articlesCount / LIMIT);
+ const currentPage = parseInt(searchParams.get("page") ?? "1", 10);
+
+ return (
+
+ );
+}
+```
+
+이것으로 완료되었습니다. 탭 목록도 비슷하게 구현할 수 있지만, 인증을 구현할 때까지 잠시 보류하겠습니다. 그런데 말이 나왔으니!
+
+### 인증
+
+인증에는 두 개의 페이지가 관련됩니다 - 로그인과 회원가입입니다. 이들은 대부분 동일하므로 필요한 경우 코드를 재사용할 수 있도록 `sign-in`이라는 동일한 슬라이스에 유지하는 것이 합리적입니다.
+
+`pages/sign-in`의 `ui` 세그먼트에 다음 내용으로 `RegisterPage.tsx`를 만드세요.
+
+```tsx title="pages/sign-in/ui/RegisterPage.tsx"
+import { Form, Link, useActionData } from "@remix-run/react";
+
+import type { register } from "../api/register";
+
+export function RegisterPage() {
+ const registerData = useActionData();
+
+ return (
+
+ );
+}
+```
+
+이것으로 우리의 글 읽기 페이지도 완성되었습니다! 이제 작성자를 팔로우하고, 글에 좋아요를 누르고, 댓글을 남기는 버튼들이 예상대로 작동해야 합니다.
+
+
+
+### 글 작성 페이지
+
+이것은 이 튜토리얼에서 다룰 마지막 페이지이며, 여기서 가장 흥미로운 부분은 폼 데이터를 어떻게 검증할 것인가 입니다.
+
+페이지 자체인 `article-edit/ui/ArticleEditPage.tsx`는 꽤 간단할 것이며, 추가적인 복잡성은 다른 두 개의 컴포넌트로 숨겨질 것입니다.
+
+```tsx title="pages/article-edit/ui/ArticleEditPage.tsx"
+import { Form, useLoaderData } from "@remix-run/react";
+
+import type { loader } from "../api/loader";
+import { TagsInput } from "./TagsInput";
+import { FormErrors } from "./FormErrors";
+
+export function ArticleEditPage() {
+ const article = useLoaderData();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+이 페이지는 현재 글(새로 작성하는 경우가 아니라면)을 가져와서 해당하는 폼 필드를 채웁니다. 이전에 본 적이 있습니다. 흥미로운 부분은 `FormErrors`인데, 이는 검증 결과를 받아 사용자에게 표시할 것입니다. 한번 살펴보겠습니다.
+
+```tsx title="pages/article-edit/ui/FormErrors.tsx"
+import { useActionData } from "@remix-run/react";
+import type { action } from "../api/action";
+
+export function FormErrors() {
+ const actionData = useActionData();
+
+ return actionData?.errors != null ? (
+
+ {actionData.errors.map((error) => (
+
{error}
+ ))}
+
+ ) : null;
+}
+```
+
+여기서는 우리의 액션이 `errors` 필드, 즉 사람이 읽을 수 있는 오류 메시지 배열을 반환할 것이라고 가정하고 있습니다. 곧 액션에 대해 다루겠습니다.
+
+또 다른 컴포넌트는 태그 입력입니다. 이는 단순한 입력 필드에 선택된 태그의 추가적인 미리보기가 있는 것입니다. 여기에는 특별한 것이 없습니다:
+
+```tsx title="pages/article-edit/ui/TagsInput.tsx"
+import { useEffect, useRef, useState } from "react";
+
+export function TagsInput({
+ name,
+ defaultValue,
+}: {
+ name: string;
+ defaultValue?: Array;
+}) {
+ const [tagListState, setTagListState] = useState(defaultValue ?? []);
+
+ function removeTag(tag: string): void {
+ const newTagList = tagListState.filter((t) => t !== tag);
+ setTagListState(newTagList);
+ }
+
+ const tagsInput = useRef(null);
+ useEffect(() => {
+ tagsInput.current && (tagsInput.current.value = tagListState.join(","));
+ }, [tagListState]);
+
+ return (
+ <>
+
+ setTagListState(e.target.value.split(",").filter(Boolean))
+ }
+ />
+
+ >
+ );
+}
+```
+
+이제 API 부분입니다. 로더는 URL을 살펴보고, 글 슬러그가 포함되어 있다면 기존 글을 수정하는 것이므로 해당 데이터를 로드해야 합니다. 그렇지 않으면 아무것도 반환하지 않습니다. 그 로더를 만들어 봅시다.
+
+```ts title="pages/article-edit/api/loader.ts"
+import { json, type LoaderFunctionArgs } from "@remix-run/node";
+import type { FetchResponse } from "openapi-fetch";
+
+import { GET, requireUser } from "shared/api";
+
+async function throwAnyErrors(
+ responsePromise: Promise>,
+) {
+ const { data, error, response } = await responsePromise;
+
+ if (error !== undefined) {
+ throw json(error, { status: response.status });
+ }
+
+ return data as NonNullable;
+}
+
+export const loader = async ({ params, request }: LoaderFunctionArgs) => {
+ const currentUser = await requireUser(request);
+
+ if (!params.slug) {
+ return { article: null };
+ }
+
+ return throwAnyErrors(
+ GET("/articles/{slug}", {
+ params: { path: { slug: params.slug } },
+ headers: { Authorization: `Token ${currentUser.token}` },
+ }),
+ );
+};
+```
+
+액션은 새로운 필드 값들을 받아 우리의 데이터 스키마를 통해 실행하고, 모든 것이 올바르다면 이러한 변경사항을 백엔드에 커밋합니다. 이는 기존 글을 업데이트하거나 새 글을 생성하는 방식으로 이루어집니다.
+
+```tsx title="pages/article-edit/api/action.ts"
+import { json, redirect, type ActionFunctionArgs } from "@remix-run/node";
+
+import { POST, PUT, requireUser } from "shared/api";
+import { parseAsArticle } from "../model/parseAsArticle";
+
+export const action = async ({ request, params }: ActionFunctionArgs) => {
+ try {
+ const { body, description, title, tags } = parseAsArticle(
+ await request.formData(),
+ );
+ const tagList = tags?.split(",") ?? [];
+
+ const currentUser = await requireUser(request);
+ const payload = {
+ body: {
+ article: {
+ title,
+ description,
+ body,
+ tagList,
+ },
+ },
+ headers: { Authorization: `Token ${currentUser.token}` },
+ };
+
+ const { data, error } = await (params.slug
+ ? PUT("/articles/{slug}", {
+ params: { path: { slug: params.slug } },
+ ...payload,
+ })
+ : POST("/articles", payload));
+
+ if (error) {
+ return json({ errors: error }, { status: 422 });
+ }
+
+ return redirect(`/article/${data.article.slug ?? ""}`);
+ } catch (errors) {
+ return json({ errors }, { status: 400 });
+ }
+};
+```
+
+스키마는 `FormData`를 위한 파싱 함수로도 작동하여, 깨끗한 필드를 편리하게 얻거나 마지막에 처리할 오류를 던질 수 있게 해줍니다. 그 파싱 함수는 다음과 같이 보일 수 있습니다.
+
+```tsx title="pages/article-edit/model/parseAsArticle.ts"
+export function parseAsArticle(data: FormData) {
+ const errors = [];
+
+ const title = data.get("title");
+ if (typeof title !== "string" || title === "") {
+ errors.push("Give this article a title");
+ }
+
+ const description = data.get("description");
+ if (typeof description !== "string" || description === "") {
+ errors.push("Describe what this article is about");
+ }
+
+ const body = data.get("body");
+ if (typeof body !== "string" || body === "") {
+ errors.push("Write the article itself");
+ }
+
+ const tags = data.get("tags");
+ if (typeof tags !== "string") {
+ errors.push("The tags must be a string");
+ }
+
+ if (errors.length > 0) {
+ throw errors;
+ }
+
+ return { title, description, body, tags: data.get("tags") ?? "" } as {
+ title: string;
+ description: string;
+ body: string;
+ tags: string;
+ };
+}
+```
+
+물론 이는 다소 길고 반복적이지만, 사람이 읽을 수 있는 오류 메시지를 위해 우리가 지불해야 하는 대가입니다. 이것은 Zod 스키마일 수도 있지만, 그렇게 하면 프론트엔드에서 오류 메시지를 렌더링해야 하고, 이 폼은 그런 복잡성을 감당할 만한 가치가 없습니다.
+
+마지막 단계로 - 페이지, 로더, 그리고 액션을 라우트에 연결합니다. 우리는 생성과 편집을 모두 깔끔하게 지원하므로 `editor._index.tsx`와 `editor.$slug.tsx` 모두에서 동일한 것을 내보낼 수 있습니다.
+
+```tsx title="pages/article-edit/index.ts"
+export { ArticleEditPage } from "./ui/ArticleEditPage";
+export { loader } from "./api/loader";
+export { action } from "./api/action";
+```
+
+```tsx title="app/routes/editor._index.tsx, app/routes/editor.$slug.tsx (same content)"
+import { ArticleEditPage } from "pages/article-edit";
+
+export { loader, action } from "pages/article-edit";
+
+export default ArticleEditPage;
+```
+
+이제 완료되었습니다! 로그인하고 새 글을 작성해보세요. 또는 글을 "잊어버리고" 검증이 작동하는 것을 확인해보세요.
+
+
+
+프로필과 설정 페이지는 글 읽기와 편집기 페이지와 매우 유사하므로, 독자인 여러분의 연습 과제로 남겨두겠습니다 :)
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md
new file mode 100644
index 0000000000..60fe0d2016
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/auth.md
@@ -0,0 +1,225 @@
+---
+sidebar_position: 1
+---
+
+# Authentication
+
+보통 인증은 세 가지 주요 단계로 이루어집니다:
+
+1. 사용자로부터 로그인 정보(아이디, 비밀번호 등)을 수집합니다.
+2. 백엔드 서버로 해당 로그인 정보을 전송합니다.
+3. 인증 후 발급받은 토큰을 저장하여 이후 요청에 사용합니다.
+
+## 사용자 로그인 정보 수집 방법
+
+앱에서 사용자로부터 로그인 정보를 수집하는 방법을 알아보겠습니다. 만약에 OAuth를 사용하는 경우, OAuth 제공자의 로그인 페이지를 사용하여 [3단계](#how-to-store-the-token-for-authenticated-requests)로 바로 넘어갈 수 있습니다.
+
+### 전용 로그인 페이지 만들기
+
+웹사이트에서 사용자 이름과 비밀번호를 입력하는 로그인 페이지를 제공하는 것이 일반적입니다. 이러한 페이지들은 구조가 단순하여 별도의 복잡한 분해 작업이 필요하지 않습니다. 다만, 로그인과 회원가입 양식은 외형이 비슷하기 때문에, 경우에 따라 두 양식을 하나의 페이지에서 통합하여 제공하기도 합니다.
+
+- 📂 pages
+ - 📂 login
+ - 📂 ui
+ - 📄 LoginPage.tsx (or your framework's component file format)
+ - 📄 RegisterPage.tsx
+ - 📄 index.ts
+ - other pages…
+
+로그인과 회원가입 컴포넌트를 별도로 만들고, 필요에 따라 index 파일에서 export 할 수 있습니다. 이 컴포넌트들은 사용자로부터 로그인 정보을 입력받는 폼을 포함합니다.
+
+### 로그인 다이얼로그 만들기
+
+앱의 어디서나 사용할 수 있는 로그인 다이얼로그가 필요하다면, 이 다이얼로그를 재사용 가능한 위젯으로 만드는 것이 좋습니다. 이렇게 하면 불필요한 세분화를 피하면서도 어떤 페이지에서나 쉽게 로그인 다이얼로그를 띄울 수 있습니다.
+
+- 📂 widgets
+ - 📂 login-dialog
+ - 📂 ui
+ - 📄 LoginDialog.tsx
+ - 📄 index.ts
+ - other widgets…
+
+가이드 나머지 부분은 전용 페이지 방식에 대해 설명하고 있지만, 동일한 원칙을 로그인 다이얼로그에도 적용할 수 있습니다.
+
+### 클라이언트 측 검증
+
+특히 회원가입의 경우, 사용자가 입력한 내용에 문제가 있을 때 빠르게 피드백을 제공하기 위해 클라이언트 측 검증을 수행하는 것이 좋습니다. 이를 위해 로그인 페이지의 `model` 세그먼트에서 검증 로직을 구현할 수 있습니다. 예를 들어 JS/TS에서는 [Zod][ext-zod]와 같은 스키마 검증 라이브러리를 사용할 수 있습니다:
+
+```ts title="pages/login/model/registration-schema.ts"
+import { z } from "zod";
+
+export const registrationData = z.object({
+ email: z.string().email(),
+ password: z.string().min(6),
+ confirmPassword: z.string(),
+}).refine((data) => data.password === data.confirmPassword, {
+ message: "비밀번호가 일치하지 않습니다",
+ path: ["confirmPassword"],
+});
+```
+
+그런 다음, ui 세그먼트에서 이 스키마를 사용하여 사용자 입력을 검증할 수 있습니다:
+
+```tsx title="pages/login/ui/RegisterPage.tsx"
+import { registrationData } from "../model/registration-schema";
+
+function validate(formData: FormData) {
+ const data = Object.fromEntries(formData.entries());
+ try {
+ registrationData.parse(data);
+ } catch (error) {
+ // TODO: Show error message to the user
+ }
+}
+
+export function RegisterPage() {
+ return (
+
+ )
+}
+```
+
+## 로그인 정보 전송 방법
+
+로그인 정보를 백엔드 서버로 전송하기 위한 요청 함수를 작성하세요. 이 함수는 상태 관리 라이브러리나 뮤테이션 라이브러리(예: TanStack Query)를 사용하여 호출할 수 있습니다.
+
+### 요청 함수 저장 위치
+
+이 요청 함수를 저장할 수 있는 위치는 크게 두 가지입니다: `shared/api` 또는 페이지의 `api` 세그먼트입니다.
+
+#### `shared/api`에 저장하기
+
+모든 API 요청을 `shared/api`에 모아서 관리하고, 엔드포인트별로 그룹화하는 접근 방식입니다. 파일 구조는 다음과 같습니다:
+
+- 📂 shared
+ - 📂 api
+ - 📂 endpoints
+ - 📄 login.ts
+ - other endpoint functions…
+ - 📄 client.ts
+ - 📄 index.ts
+
+`📄 client.ts` 파일은 요청을 수행하는 원시 함수(예: `fetch()`)에 대한 래퍼를 포함합니다. 이 래퍼는 백엔드의 기본 URL 설정, 헤더 설정, 데이터 직렬화 등을 처리합니다.
+
+```ts title="shared/api/endpoints/login.ts"
+import { POST } from "../client";
+
+export function login({ email, password }: { email: string, password: string }) {
+ return POST("/login", { email, password });
+}
+```
+
+```ts title="shared/api/index.ts"
+export { login } from "./endpoints/login";
+```
+
+#### 페이지의 `api` 세그먼트에 저장하기
+
+로그인 요청이 특정 페이지에만 필요한 경우, 로그인 페이지의 `api` 세그먼트에 함수를 저장할 수 있습니다:
+
+- 📂 pages
+ - 📂 login
+ - 📂 api
+ - 📄 login.ts
+ - 📂 ui
+ - 📄 LoginPage.tsx
+ - 📄 index.ts
+ - other pages…
+
+```ts title="pages/login/api/login.ts"
+import { POST } from "shared/api";
+
+export function login({ email, password }: { email: string, password: string }) {
+ return POST("/login", { email, password });
+}
+```
+
+이 함수는 페이지의 공개 API에서 내보낼 필요가 없습니다. 로그인 요청이 다른 곳에서 필요할 가능성이 낮기 때문입니다.
+
+### 이중 인증(2FA)
+
+앱이 이중 인증(2FA)을 지원하는 경우, 사용자가 일회용 비밀번호(OTP)를 입력할 수 있는 별도의 페이지로 이동해야 할 수 있습니다. 일반적으로 `POST /login` 요청은 사용자가 2FA를 활성화했음을 나타내는 플래그가 포함된 사용자 객체를 반환합니다. 이 플래그가 설정되면 사용자를 2FA 페이지로 리디렉션해야 합니다.
+
+2FA 페이지는 로그인과 밀접하게 연관되어 있으므로 Pages 레이어의 `login` 슬라이스에 함께 저장하는 것이 좋습니다.
+
+이중 인증을 처리하기 위해서는 `login()` 함수와 유사한 또 다른 요청 함수가 필요할 것입니다. 이러한 함수들은 `Shared`나 로그인 페이지의 `api` 세그먼트에 함께 배치할 수 있습니다.
+
+## 인증된 요청의 토큰 저장 방법 {#how-to-store-the-token-for-authenticated-requests}
+
+인증 방식이 로그인/비밀번호, OAuth, 2단계 인증 등 어떤 것이든, 결국 토큰이 발급됩니다. 이 토큰은 이후 요청에서 사용자 식별을 위해 저장되어야 합니다.
+
+웹 애플리케이션에서는 **쿠키**를 사용해 토큰을 저장하는 것이 가장 일반적이고 이상적인 방법입니다. 쿠키를 사용하면 토큰을 수동으로 관리할 필요가 없으며, 복잡한 처리를 줄일 수 있습니다. 만약 서버 사이드 렌더링을 지원하는 프레임워크(예: [Remix][ext-remix])를 사용 중이라면, 서버 사이드 쿠키 인프라를 `shared/api`에 저장하는 것이 좋습니다. Remix를 사용하는 예시는 튜토리얼의 [인증 섹션][tutorial-authentication]에서 확인할 수 있습니다.
+
+그러나 쿠키를 사용할 수 없는 상황에서는, 토큰을 직접 관리해야 합니다. 이 경우, 토큰 만료 시 갱신 로직을 함께 구현해야 할 수도 있습니다. 이 경우, 토큰 만료 시 갱신 로직을 함께 구현해야 합니다. FSD에서는 토큰을 저장할 수 있는 다양한 방법이 있습니다.
+
+### Shared에 저장하기
+
+`shared/api`에 저장하는 접근 방식은 API 클라이언트와 잘 맞아떨어집니다. 인증이 필요한 다른 요청 함수에서 이 토큰을 쉽게 사용할 수 있기 때문입니다. API 클라이언트에서 반응형 스토어나 모듈 수준 변수를 사용해 토큰을 저장하고, `login()/logout()` 함수에서 해당 상태를 업데이트할 수 있습니다.
+
+토큰 자동 갱신은 API 클라이언트에서 미들웨어 형태로 구현할 수 있습니다. 모든 요청마다 실행되며, 아래와 같은 방식으로 동작합니다:
+
+- 사용자가 로그인하면 액세스 토큰과 갱신 토큰을 저장합니다.
+- 인증이 필요한 요청을 수행합니다.
+- 토큰이 만료되어 요청이 실패하면, 갱신 토큰을 사용해 새로운 토큰을 요청하고 저장한 후, 원래 요청을 다시 시도합니다.
+
+이 방법의 단점 중 하나는 토큰 관리 로직이 요청 로직과 같은 위치에 있어, 복잡해질 수 있다는 점입니다. 간단한 경우에는 문제가 없겠지만, 토큰 관리 로직이 복잡한 경우에는 요청과 관리 로직을 분리하는 것이 좋습니다. 요청 및 API 클라이언트는 `shared/api`에 두고, 토큰 관리 로직은 `shared/auth`에 두는 방식으로 나눌 수 있습니다.
+
+또 다른 단점은 백엔드가 토큰과 함께 현재 사용자 정보를 반환하는 경우, 이 정보를 별도로 저장하거나 `/me` 또는 `/users/current`와 같은 엔드포인트에서 다시 요청해야 한다는 점입니다.
+
+### Entities에 저장하기
+
+FSD 프로젝트에서는 사용자 엔티티 또는 현재 사용자 엔티티를 사용하는 것이 일반적입니다. 두 엔티티는 같은 것을 가리킬 수도 있습니다.
+
+:::note
+
+**현재 사용자**는 "viewer" 또는 "me"라고도 합니다. 이는 권한과 개인 정보를 가진 단일 인증 사용자와 공개적으로 접근 가능한 정보로 구성된 모든 사용자 목록을 구별하기 위해 사용됩니다.
+
+:::
+
+User 엔티티에 토큰을 저장하려면 `model` 세그먼트에 반응형 스토어를 생성해야 합니다. 이 스토어는 토큰과 사용자 객체를 모두 포함할 수 있습니다.
+
+API 클라이언트는 일반적으로 `shared/api` 정의되거나 엔티티 전체에 분산되어 있습니다. 따라서 주요 과제는 레이어의 임포트 규칙([import rule on layers][import-rule-on-layers])을 위반하지 않으면서 다른 요청에서도 토큰을 사용할 수 있도록 하는 것입니다.
+
+> 레이어 규칙: 슬라이스의 모듈은 자기보다 낮은 레이어에 위치한 다른 슬라이스만 임포트할 수 있습니다.
+
+이 문제를 해결하기 위한 몇 가지 방법은 다음과 같습니다:
+
+1. **요청 시마다 토큰 수동 전달**
+ 이 방법은 가장 간단하지만, 번거롭고 타입 안전성이 보장되지 않으면 실수가 발생할 가능성이 큽니다. 또한 Shared의 API 클라이언트에 미들웨어 패턴을 적용하기 어렵습니다.
+2. **앱 전역에서 글로벌 스토어로 토큰 관리**
+ 토큰을 context나 `localStorage`에 저장하고, `shared/api`에 토큰 접근 키를 보관합니다. 토큰의 반응형 저장소는 User 엔터티에서 내보내며, 필요한 경우 context Provider는 App 레이어에서 설정합니다. 이 방법은 API 클라이언트 설계를 유연하게 만들지만, 상위 레이어에 context 제공이 필요하다는 암묵적인 의존성을 발생시킵니다. 따라서 context나 `localStorage`가 제대로 설정되지 않았을 경우, 유용한 오류 메시지를 제공하는 것이 좋습니다.
+3. **토큰 변경 시 API 클라이언트 업데이트**
+ 반응형 스토어를 활용해 엔티티의 스토어가 변경될 때마다 API 클라이언트의 토큰 스토어를 업데이트하는 구독(subscribe)을 생성할 수 있습니다. 이 방법은 상위 계층에 암묵적인 의존성을 만든다는 점에서는 이전 해결책과 비슷하지만, 이 방법은 더 "명령형(push)" 접근이고, 이전 방법은 더 "선언형(pull)" 접근입니다.
+
+엔티티의 `model`에 토큰을 저장하여 문제를 해결하면, 토큰 관리와 관련된 더 많은 비즈니스 로직을 추가할 수 있습니다. 예를 들어, `model` 세그먼트에 토큰 만료 시 갱신하는 로직을 추가하거나, 일정 시간이 지나면 토큰을 무효화하는 로직을 포함할 수 있습니다.
+백엔드에 요청을 보내야 하는 경우에는 User 엔티티의 api 세그먼트나 `shared/api`를 사용할 수 있습니다.
+
+### Pages/Widgets에 저장하기 (권장하지 않음)
+
+애플리케이션 전역에 적용되는 상태(예: 액세스 토큰)를 페이지나 위젯에 저장하는 것은 권장되지 않습니다. 예를 들어, 로그인 페이지의 `model` 세그먼트에 토큰 스토어를 배치하는 대신, 이 아티클에서 제시한 처음 두 해결책인 Shared나 Entities를 사용하는 것이 권장됩니다.
+
+## 로그아웃 및 토큰 무효화
+
+로그아웃 기능은 애플리케이션에서 중요한 기능이지만, 이를 위한 별도의 페이지는 없는 경우가 많습니다. 이 기능은 백엔드에 인증된 요청을 보내고, 토큰 스토어를 업데이트하는 작업으로 구성됩니다.
+
+모든 요청을 `shared/api`에 보관했다면, 로그인 함수 근처에 로그아웃 요청 함수를 두는 것이 좋습니다. 그렇지 않은 경우, 로그아웃 버튼이 있는 위치 근처에 로그아웃 요청 함수를 배치할 수 있습니다. 예를 들어, 모든 페이지에 나타나는 헤더 위젯에 로그아웃 링크가 있다면, 해당 요청을 그 위젯의 `api` 세그먼트에 배치하는 것이 좋습니다.
+
+토큰 스토어에 대한 업데이트는 로그아웃 버튼이 위치한 곳(예: 헤더 위젯)에서 트리거되어야 합니다. 이 요청과 스토어 업데이트를 해당 위젯의 `model` 세그먼트에서 결합할 수 있습니다.
+
+### 자동 로그아웃
+
+로그아웃 요청 실패나 로그인 토큰 갱신 실패 시를 대비해 안전장치를 마련하는 것도 중요합니다. 이 두 경우 모두 토큰 스토어를 비워야 합니다. 토큰을 Entities에 저장하는 경우, 이 로직은 `model` 세그먼트에 배치할 수 있습니다. 토큰을 Shared에 저장하는 경우, 이 로직을 `shared/api`에 포함하면 세그먼트가 너무 복잡해질 수 있습니다. 따라서 토큰 관리 로직을 별도의 세그먼트(예: `shared/auth`)로 분리하는 것도 고려해볼 만합니다.
+
+[tutorial-authentication]: /docs/get-started/tutorial#authentication
+[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers
+[ext-remix]: https://remix.run
+[ext-zod]: https://zod.dev
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx
new file mode 100644
index 0000000000..78b29b29c0
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/index.mdx
@@ -0,0 +1,36 @@
+---
+hide_table_of_contents: true
+---
+
+# Examples
+
+
+방법론 적용에 대한 예시들
+
+
+## Main
+
+import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx"
+import { UserSwitchOutlined, LayoutOutlined, FontSizeOutlined } from "@ant-design/icons";
+
+
+
+
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md
new file mode 100644
index 0000000000..df57fa83f7
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/page-layout.md
@@ -0,0 +1,104 @@
+---
+sidebar_position: 3
+---
+
+# Page layouts
+
+이 가이드는 여러 페이지가 같은 기본 구조를 공유하고, 주요 내용만 다른 경우 사용할 수 있는 _페이지 레이아웃_ 에 대해 설명합니다.
+
+:::info
+
+이 가이드에서 다루지 않는 질문이 있으신가요? 오른쪽 파란색 버튼을 눌러 피드백을 남겨주세요. 여러분의 의견을 반영해 가이드를 확장해 나가겠습니다!
+
+:::
+
+## 간단한 레이아웃
+
+간단한 레이아웃 예시로 설명 해 보겠습니다. 이 페이지는 사이트 내비게이션이 포함된 헤더, 두 개의 사이드바, 외부 링크가 포함된 푸터로 구성되어 있습니다. 복잡한 비즈니스 로직은 없으며, 동적인 부분은 사이드바와 헤더 오른쪽에 있는 테마 전환 버튼뿐입니다. 이러한 레이아웃은 shared/ui 또는 app/layouts에 포함시킬 수 있으며, props를 통해 전달받은 사이드바 콘텐츠를 표시합니다.
+
+```tsx title="shared/ui/layout/Layout.tsx"
+import { Link, Outlet } from "react-router-dom";
+import { useThemeSwitcher } from "./useThemeSwitcher";
+
+export function Layout({ siblingPages, headings }) {
+ const [theme, toggleTheme] = useThemeSwitcher();
+
+ return (
+
+
+
+
+
+
+
+ {/* 여기에 주요 콘텐츠가 들어갑니다 */}
+
+
+
+
+ );
+}
+```
+
+```ts title="shared/ui/layout/useThemeSwitcher.ts"
+export function useThemeSwitcher() {
+ const [theme, setTheme] = useState("light");
+
+ function toggleTheme() {
+ setTheme(theme === "light" ? "dark" : "light");
+ }
+
+ useEffect(() => {
+ document.body.classList.remove("light", "dark");
+ document.body.classList.add(theme);
+ }, [theme]);
+
+ return [theme, toggleTheme] as const;
+}
+```
+
+사이드바의 구체적인 코드는 여러분 상상에 맡기겠습니다 😉.
+
+## layout에 widget 사용하기
+
+상황에 따라 layout에 특정 비즈니스 로직을 추가하고 싶을 때가 있습니다. 특히 [React Router][ext-react-router]와 같은 라우터를 사용해 깊이 중첩된 경로를 다룰 때 이러한 요구가 발생합니다. 이러한 경우 layout을 shared나 widgets 폴더에 두는 것이 어려울 수 있습니다. 이는 [layer에 대한 import 규칙][import-rule-on-layers] 때문입니다:
+
+> slice의 module은 자신보다 하위 layers에 위치한 다른 slice만 import할 수 있습니다.
+
+이 문제가 정말 중요한지 먼저 고려해 봐야 합니다. 레이아웃이 _정말로 필요한가요?_ 그리고 그 레이아웃이 _정말로 widget이어야 할까요?_ 만약 해당 비즈니스 로직이 2-3개의 페이지에서만 사용되고, 레이아웃이 그 widget을 감싸는 역할이라면, 다음 두 가지 방법을 고려해 보세요:
+
+1. **App 레이어에서 인라인으로 레이아웃 작성하기**
+ App 레이어에서 직접 레이아웃을 정의하는 것이 좋습니다. 이렇게 하면 중첩된 라우터를 사용할 때 특정 경로 그룹에만 해당 레이아웃을 적용할 수 있어 유연하게 사용할 수 있습니다.
+
+2. **복사하여 붙여넣기**
+ 코드 추상화는 항상 좋은 선택은 아닙니다. 특히 레이아웃은 자주 변경되지 않기 때문에, 필요한 경우 해당 페이지만 수정하는 것이 더 효율적일 수 있습니다. 이렇게 하면 다른 페이지에 영향을 주지 않고 수정할 수 있습니다. 팀원들이 다른 페이지를 수정하는 걸 잊을까 봐 걱정된다면, 페이지 간의 관계를 주석으로 남겨보세요. 큰 프로젝트에서도 협업이 더 편해질 거예요.
+
+위의 내용이 적절하지 않은 경우, 레이아웃에 widget을 포함하는 두 가지 해결책이 있습니다:
+
+1. **render props나 slots 사용하기**
+ 대부분의 프레임워크에서는 컴포넌트 내부에 표시될 UI 요소를 외부에서 전달할 수 있는 기능을 제공합니다. React에서는 [render props][ext-render-props]라고 하며, Vue에서는 [slots][ext-vue-slots]이라고 부릅니다.
+2. **레이아웃을 App 레이어로 이동하기**
+ 레이아웃을 `app/layouts` 등 App 레이어에 저장하고 원하는 widget을 구성할 수도 있습니다.
+
+## 추가 자료
+
+React 및 Remix(React Router와 유사)의 인증 레이아웃 구축에 대한 예시는 [튜토리얼][tutorial]에서 확인하실 수 있습니다.
+
+
+[tutorial]: /docs/get-started/tutorial
+[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers
+[ext-react-router]: https://reactrouter.com/
+[ext-render-props]: https://www.patterns.dev/react/render-props-pattern/
+[ext-vue-slots]: https://vuejs.org/guide/components/slots
+
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md
new file mode 100644
index 0000000000..5b07e91060
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/examples/types.md
@@ -0,0 +1,442 @@
+---
+sidebar_position: 2
+---
+
+# Types
+
+이 가이드는 Typescript와 같은 정적 타입 언어의 데이터 타입을 다루는 방법과 FSD 구조 내에서 타입이 어떻게 활용되는지 설명합니다.
+
+:::info
+
+이 가이드에서 다루지 않는 질문이 있으신가요? 오른쪽 파란색 버튼을 눌러 피드백을 남겨주세요. 여러분의 의견을 반영해 가이드를 확장해 나가겠습니다!
+
+:::
+
+## 유틸리티 타입
+
+유틸리티 타입은 자체로 큰 의미를 가지지는 않지만, 다른 타입과 자주 사용되는 경우가 많은 타입입니다. 예를 들어, 배열의 값을 나타내는 ArrayValues 타입을 정의할 수 있습니다.
+
+
+
+프로젝트에서 이러한 유틸리티 타입을 활용하려면, [`type-fest`][ext-type-fest] 같은 라이브러리를 설치하거나, 직접 `shared/lib`에 유틸리티 타입을 모아 라이브러리를 구축할 수 있습니다. 새로 추가할 타입과 이 라이브러리에 속하지 않는 타입을 명확하게 구분하는 것이 중요합니다. 예를 들어, 이를 `shared/lib/utility-types`로 수정하고 유틸리티 타입들에 대한 설명을 포함한 README 파일을 추가하는 것도 좋은 방법입니다.
+
+하지만 유틸리티 타입을 너무 많이 재사용하려고 하지 않는 것도 중요합니다. 재사용할 수 있다고 해서 꼭 모든 곳에서 사용할 필요는 없습니다. 모든 유틸리티 타입을 공유 폴더에 넣기보다는, 상황에 따라 필요한 파일 가까에에 두는 것이 더 좋을 떄도 있습니다.
+
+- 📂 pages
+ - 📂 home
+ - 📂 api
+ - 📄 ArrayValues.ts (유틸리티 타입)
+ - 📄 getMemoryUsageMetrics.ts (유틸리티 타입을 사용하는 코드)
+
+:::warning
+
+`shared/types` 폴더를 생성하거나 각 슬라이스에 `types`라는 세그먼트를 추가하고 싶은 마음이 들 수 있지만, 그렇게 하지 않는 것이 좋습니다.
+`types`라는 카테고리는 `components`나 `hooks`와 마찬가지로 내용이 무엇인지를 설명할 뿐, 코드의 목적을 명확히 설명하지 않습니다. 슬라이스는 해당 코드의 목적을 정확히 설명할 수 있어야 합니다.
+
+:::
+
+## 비즈니스 엔티티 및 상호 참조 관계
+
+앱에서 가장 중요한 타입 중 하나는 비즈니스 엔티티, 즉 앱에서 다루는 객체들 입니다.
+예를 들어, 음악 스트리밍 앱에서는 _Song_, _Album_ 등이 비즈니스 엔티티가 될 수 있습니다.
+
+비즈니스 엔티티는 주로 백엔드 바탕이기 떄문에, 백엔드 응답을 타입으로 정의하는 것이 첫 번째 단계입니다.
+각 엔드포인트에 대한 요청 함수와 그 응답을 타입으로 지정하는 것이 좋습니다, 추가적인 타입 안정성을 위해 [Zod][ext-zod]와 같은 스키마 검증 라이브러리를 사용해 응답을 검증할 수도 있습니다.
+
+예를 들어, 모든 요청을 Shared에 보관하는 경우 이렇게 작성할 수 있습니다.
+
+```ts title="shared/api/songs.ts"
+import type { Artist } from "./artists";
+
+interface Song {
+ id: number;
+ title: string;
+ artists: Array;
+}
+
+export function listSongs() {
+ return fetch('/api/songs').then((res) => res.json() as Promise>);
+}
+```
+
+`Song` 타입은 다른 엔티티인 `Artist`를 참조합니다. 이와 같이 요청 관련 코드들을 Shared에 관리하면, 타입들의 서로 얽혀 있을 떄 관리가 용이해집니다. 만약 이 함수를 `entities/song/api`에 보관했다면, `entities/artist`에서 간단히 가져오는 것이 어려웠을 것 입니다. FSD 구조에서는 [레이어별 import 규칙][import-rule-on-layers]을 통해 슬라이스 간의 교차 import를 제한하고 있기 떄문입니다:
+
+> 슬라이스 안에 있는 모듈은 계층적으로 더 낮은 레이어에 위치한 슬라이스만 가져올 수 있습니다.
+
+이 문제를 해결하기 위한 두 가지 방법은 다음과 같습니다:
+
+1. **타입 매개변수화**
+ 타입이 다른 엔티티와 연결될 때, 타입 매개변수를 통해 처리할 수 있습니다. 예를 들어, Song 타입에 ArtistType이라는 제약 조건을 설정할 수 있습니다.
+
+ ```ts title="entities/song/model/song.ts"
+ interface Song {
+ id: number;
+ title: string;
+ artists: Array;
+ }
+ ```
+
+ 이 방법은 일부 타입에 더 적합합니다. 예를 들어, `Cart = { items: Array }`처럼 간단한 타입은 다양한 제품 타입을 지원하기 쉽게 할 수 있습니다. 하지만 `Country`와 `City`처럼 더 밀접하게 연결된 타입은 분리하기 어렵습니다.
+
+2. **Cross-import (공개 API를 사용해 관리하기)**
+ FSD에서 엔티티 간 cross-imports를 허용하기 위해서는 공개 API를 사용할 수 있습니다. 예를 들어, `song`, `artist`, `playlist`라는 엔티티가 있고, 후자의 두 엔티티가 `song`을 참조해야 한다고 가정합니다. 이 경우, `song` 엔티티 내에 `artist`와 `playlist`용 공개 API를 따로 `@x` 표기를 만들어 사용할 수 있습니다.
+
+ - 📂 entities
+ - 📂 song
+ - 📂 @x
+ - 📄 artist.ts (artist entities를 가져오기 위한 public API)
+ - 📄 playlist.ts (playlist.ts (playlist entities를 가져오기 위한 public API))
+ - 📄 index.ts (일반적인 public API)
+
+ 파일 `📄 entities/song/@x/artist.ts`의 내용은 `📄 entities/song/index.ts`와 유사합니다:
+
+ ```ts title="entities/song/@x/artist.ts"
+ export type { Song } from "../model/song.ts";
+ ```
+
+ 따라서 `📄 entities/artist/model/artist.ts` 파일은 다음과 같이 `Song`을 가져올 수 있습니다:
+
+ ```ts title="entities/artist/model/artist.ts"
+ import type { Song } from "entities/song/@x/artist";
+
+ export interface Artist {
+ name: string;
+ songs: Array;
+ }
+ ```
+
+ 이렇게 엔티티 간 명시적으로 연결을 해두면 의존 관계를 파악하고 도메인 분리 수준을 유지하기 쉬워집니다.
+
+## 데이터 전송 객체와 mappers {#data-transfer-objects-and-mappers}
+
+데이터 전송 객체(Data Transfer Object, DTO)는 백엔드에서 오는 데이터의 구조를 나타내는 용어입니다. 떄로는 DTO를 그대로 사용하는 것이 편리할 수 있지만, 경우에 따라 프론트엔드에서는 불편할 수 있습니다. 이때 매퍼를 사용해 DTO를 더 편리한 형태로 변환합니다.
+
+### DTO의 위치
+
+백엔드 타입이 별도의 패키지에 있는 경우(예: 프론트엔드와 백엔드에서 코드를 공유하는 경우) DTO를 해당 패키지에서 가져와 사용하면 됩니다. 백엔드와 프론트엔드 간 코드 공유가 없다면, 프론트엔드 코드베이스 어딘가에 DTO를 보관해야 하는데, 이를 아래에서 다루어 보겠습니다.
+
+`shared/api`에 요청 함수가 있다면, DTO 역시 해당 함수 바로 옆에 두는 것이 좋습니다:
+
+```ts title="shared/api/songs.ts"
+import type { ArtistDTO } from "./artists";
+
+interface SongDTO {
+ id: number;
+ title: string;
+ artist_ids: Array;
+}
+
+export function listSongs() {
+ return fetch('/api/songs').then((res) => res.json() as Promise>);
+}
+```
+
+앞에서 언급한 것처럼, 요청과 DTO를 shared에 두면 다른 DTO를 참조하기가 용이합니다.
+
+### Mappers의 위치
+
+Mappers는 DTO를 받아 변환하는 역할을 하므로, DTO 정의와 가까운 위치에 두는 것이 좋습니다. 만약 요청과 DTO가 `shared/api`에 정의되어 있다면, mappers도 그곳에 위치하는 것이 적절합니다.
+
+```ts title="shared/api/songs.ts"
+import type { ArtistDTO } from "./artists";
+
+interface SongDTO {
+ id: number;
+ title: string;
+ disc_no: number;
+ artist_ids: Array;
+}
+
+interface Song {
+ id: string;
+ title: string;
+ /** 노래의 전체 제목, 디스크 번호까지 포함된 제목입니다. */
+ fullTitle: string;
+ artistIds: Array;
+}
+
+function adaptSongDTO(dto: SongDTO): Song {
+ return {
+ id: String(dto.id),
+ title: dto.title,
+ fullTitle: `${dto.disc_no} / ${dto.title}`,
+ artistIds: dto.artist_ids.map(String),
+ };
+}
+
+export function listSongs() {
+ return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO));
+}
+```
+
+요청과 상태 관리 코드가 엔티티 슬라이스에 정의되어 있는 경우, mappers 역시 해당 슬라이스 내에 두는 것이 좋습니다. 이때 슬라이스 간 교차 참조가 발생하지 않도록 주의해야 합니다.
+
+```ts title="entities/song/api/dto.ts"
+import type { ArtistDTO } from "entities/artist/@x/song";
+
+export interface SongDTO {
+ id: number;
+ title: string;
+ disc_no: number;
+ artist_ids: Array;
+}
+```
+
+```ts title="entities/song/api/mapper.ts"
+import type { SongDTO } from "./dto";
+
+export interface Song {
+ id: string;
+ title: string;
+ /** 노래의 전체 제목, 디스크 번호까지 포함된 제목입니다. */
+ fullTitle: string;
+ artistIds: Array;
+}
+
+export function adaptSongDTO(dto: SongDTO): Song {
+ return {
+ id: String(dto.id),
+ title: dto.title,
+ fullTitle: `${dto.disc_no} / ${dto.title}`,
+ artistIds: dto.artist_ids.map(String),
+ };
+}
+```
+
+```ts title="entities/song/api/listSongs.ts"
+import { adaptSongDTO } from "./mapper";
+
+export function listSongs() {
+ return fetch('/api/songs').then(async (res) => (await res.json()).map(adaptSongDTO));
+}
+```
+
+```ts title="entities/song/model/songs.ts"
+import { createSlice, createEntityAdapter } from "@reduxjs/toolkit";
+
+import { listSongs } from "../api/listSongs";
+
+export const fetchSongs = createAsyncThunk('songs/fetchSongs', listSongs);
+
+const songAdapter = createEntityAdapter();
+const songsSlice = createSlice({
+ name: "songs",
+ initialState: songAdapter.getInitialState(),
+ reducers: {},
+ extraReducers: (builder) => {
+ builder.addCase(fetchSongs.fulfilled, (state, action) => {
+ songAdapter.upsertMany(state, action.payload);
+ })
+ },
+});
+```
+
+### 중첩된 DTO 처리 방법
+
+백엔드 응답에 여러 엔티티가 포함된 경우 문제가 될 수 있습니다. 예를 들어, 곡 정보에 저자의 ID뿐만 아니라 저자 객체 전체가 포함된 경우가 있을 수 있습니다. 이런 상황에서는 엔티티 간의 상호 참조를 피하기 어렵습니다. 데이터를 지우거나 백엔드 팀과 협의하지 않는 한, 이러한 경우에는 슬라이스 간 간접적인 연결 대신 명시적인 교차 참조를 사용하는 것이 좋습니다. 이를 위해 `@x` 표기법을 활용할 수 있으며, 다음은 Redux Toolkit을 사용한 예시입니다:
+
+```ts title="entities/song/model/songs.ts"
+import {
+ createSlice,
+ createEntityAdapter,
+ createAsyncThunk,
+ createSelector,
+} from '@reduxjs/toolkit'
+import { normalize, schema } from 'normalizr'
+
+import { getSong } from "../api/getSong";
+
+// Normalizr의 entities 스키마 정의
+export const artistEntity = new schema.Entity('artists')
+export const songEntity = new schema.Entity('songs', {
+ artists: [artistEntity],
+})
+
+const songAdapter = createEntityAdapter()
+
+export const fetchSong = createAsyncThunk(
+ 'songs/fetchSong',
+ async (id: string) => {
+ const data = await getSong(id)
+ // 데이터를 정규화하여 리듀서가 예측 가능한 payload를 로드할 수 있도록 합니다:
+ // `action.payload = { songs: {}, artists: {} }`
+ const normalized = normalize(data, songEntity)
+ return normalized.entities
+ }
+)
+
+export const slice = createSlice({
+ name: 'songs',
+ initialState: songAdapter.getInitialState(),
+ reducers: {},
+ extraReducers: (builder) => {
+ builder.addCase(fetchSong.fulfilled, (state, action) => {
+ songAdapter.upsertMany(state, action.payload.songs)
+ })
+ },
+})
+
+const reducer = slice.reducer
+export default reducer
+```
+
+```ts title="entities/song/@x/artist.ts"
+export { fetchSong } from "../model/songs";
+```
+
+```ts title="entities/artist/model/artists.ts"
+import { createSlice, createEntityAdapter } from '@reduxjs/toolkit'
+
+import { fetchSong } from 'entities/song/@x/artist'
+
+const artistAdapter = createEntityAdapter()
+
+export const slice = createSlice({
+ name: 'users',
+ initialState: artistAdapter.getInitialState(),
+ reducers: {},
+ extraReducers: (builder) => {
+ builder.addCase(fetchSong.fulfilled, (state, action) => {
+ // 같은 fetch 결과를 처리하며, 여기서 artists를 삽입합니다.
+ artistAdapter.upsertMany(state, action.payload.artists)
+ })
+ },
+})
+
+const reducer = slice.reducer
+export default reducer
+```
+
+이 방법은 슬라이스 분리의 이점을 다소 제한할 수 있지만, 우리가 제어할 수 없는 두 엔티티 간의 관계를 명확하게 나타냅니다. 만약 이러한 엔티티가 리팩토링되어야 한다면, 함께 리팩토링해야 할 것입니다.
+
+## 전역 타입과 Redux
+
+전역 타입은 애플리케이션 전반에서 사용되는 타입을 의미하며, 크게 두 가지로 나눌 수 있습니다:
+1. 애플리케이션 특성이 없는 제너릭 타입
+2. 애플리케이션 전체에 알고 있어야 하는 타입
+
+첫 번째 경우에는 관련 타입을 Shared 폴더 안에 적절한 세그먼트로 배치하면 됩니다. 예를 들어, 분석 전역 변수를 위한 인터페이스가 있다면 `shared/analytics`에 두는 것이 좋습니다.
+
+:::warning
+
+경고: `shared/types` 폴더를 생성하지 않는 것이 좋습니다. "타입"이라는 공통된 속성으로 관련 없는 항목들을 그룹화하면, 프로젝트에서 코드를 검색할 때 효율성이 떨어질 수 있습니다.
+
+:::
+
+두 번째 경우는 Redux를 사용하지만 RTK가 없는 프로젝트에서 자주 발생합니다. 최종 스토어 타입은 모든 리듀서를 추가한 후에만 사용 가능하지만, 이 스토어 타입은 앱 전체에서 사용하는 셀렉터에 필요합니다. 예를 들어, 일반적인 스토어 정의는 다음과 같습니다:
+
+```ts title="app/store/index.ts"
+import { combineReducers, rootReducer } from "redux";
+
+import { songReducer } from "entities/song";
+import { artistReducer } from "entities/artist";
+
+const rootReducer = combineReducers(songReducer, artistReducer);
+
+const store = createStore(rootReducer);
+
+type RootState = ReturnType;
+type AppDispatch = typeof store.dispatch;
+```
+
+`shared/store`에서 `useAppDispatch`와 `useAppSelector`와 같은 타입이 지정된 Redux 훅을 사용하는 것이 좋지만, [레이어에 대한 import 규칙][import-rule-on-layers] 떄문에 App 레이어에서 `RootState`와 `AppDispatch`를 import 할 수 없습니다.
+
+> 슬라이스의 모듈은 더 낮은 레이어에 위치한 다른 슬라이스만 import 할 수 있습니다.
+
+이 경우 권장되는 해결책은 Shared와 App 레이어 간에 암묵적인 의존성을 만드는 것입니다. `RootState`와 `AppDispatch` 두 타입은 유지보수 필요성이 적고 Redux를 사용하는 개발자들에게 익숙하므로 큰 문제 없이 사용할 수 있습니다.
+
+TypeScript에서는 다음과 같이 타입을 전역으로 선언할 수 있습니다:
+
+```ts title="app/store/index.ts"
+/* 이전 코드 블록과 동일한 내용입니다… */
+
+declare type RootState = ReturnType;
+declare type AppDispatch = typeof store.dispatch;
+```
+
+```ts title="shared/store/index.ts"
+import { useDispatch, useSelector, type TypedUseSelectorHook } from "react-redux";
+
+export const useAppDispatch = useDispatch.withTypes()
+export const useAppSelector: TypedUseSelectorHook = useSelector;
+```
+
+## 열거형
+
+**일반적으로 열거형(enum)은 사용되는 위치와 최대한 가까운 곳에 정의하는 것이 좋습니다**. 열거형이 특정 기능과 관련된 값을 나타낸다면, 해당 기능 내에 정의해야 합니다.
+
+세그먼트 선택도 사용 위치에 따라 달라져야 합니다. 예를 들어, 화면에서 토스트 위치를 나타내는 열거형이라면 ui 세그먼트에 두는 것이 좋고, 백엔드 응답 상태 등을 나타낸다면 api 세그먼트에 두는 것이 적합합니다.
+
+프로젝트 전반에서 공통으로 사용되는 열거형도 있습니다. 예를 들어, 일반적인 백엔드 응답 상태나 디자인 시스템 토큰 등이 있습니다. 이 경우 Shared에 두되, 열거형이 나타내는 것을 기준으로 세그먼트를 선택하면 됩니다 (`api`는 응답 상태, `ui`는 디자인 토큰 등).
+
+## 타입 검증 스키마와 Zod
+
+데이터가 특정 형태나 제약 조건을 충족하는지 검증하려면 검증 스키마를 정의할 수 있습니다. TypeScript에서는 [Zod][ext-zod]와 같은 라이브러리를 많이 사용합니다. 검증 스키마는 가능하면 사용하는 코드와 같은 위치에 두는 것이 좋습니다.
+
+검증 스키마는 데이터를 파싱하며, 파싱에 실패하면 오류를 발생시킵니다.([Data transfoer objects and mappers](#data-transfer-objects-and-mappers) 토론을 참조하세요.) 가장 일반적인 검증 사례 중 하나는 백엔드에서 오는 데이터에 대한 것입니다. 데이터가 스키마와 일치하지 않는 경우 요청을 실패시키기를 원하기 때문에, 보통 `api` 세그먼트에 스키마를 두는 것이 좋습니다.
+
+사용자 입력(예: 폼)으로 데이터를 받을 경우, 입력된 데이터에 대해 바로 검증이 이루어져야 합니다. 이 경우 스키마를 `ui` 세그먼트 내 폼 컴포넌트 옆에 두거나, `ui` 세그먼트가 너무 복잡하다면 `model` 세그먼트에 둘 수 있습니다.
+
+## 컴포넌트 props와 context의 타입 정의
+
+보통 props나 context 인터페이스는 이를 사용하는 컴포넌트나 컨텍스트와 같은 파일에 두는 것이 가장 좋습니다. 만약 Vue나 Svelte처럼 단일 파일 컴포넌트를 사용하는 프레임워크에서 여러 컴포넌트 간에 해당 인터페이스를 공유해야 한다면, `ui` 세그먼트 내 동일 폴더에 별도의 파일을 만들어 정의할 수 있습니다.
+
+예를 들어, React의 JSX에서는 다음과 같이 정의합니다:
+
+```ts title="pages/home/ui/RecentActions.tsx"
+interface RecentActionsProps {
+ actions: Array<{ id: string; text: string }>;
+}
+
+export function RecentActions({ actions }: RecentActionsProps) {
+ /* … */
+}
+```
+
+Vue에서 인터페이스를 별도 파일에 저장한 예는 다음과 같습니다:
+
+```ts title="pages/home/ui/RecentActionsProps.ts"
+export interface RecentActionsProps {
+ actions: Array<{ id: string; text: string }>;
+}
+```
+
+```html title="pages/home/ui/RecentActions.vue"
+
+```
+
+## Ambient 선언 파일(*.d.ts)
+
+[Vite][ext-vite]나 [ts-reset][ext-ts-reset] 같은 일부 패키지는 앱 전반에서 작동하기 위해 Ambient 선언 파일을 필요로 합니다. 이러한 파일들은 보통 크거나 복잡하지 않기 때문에 `src/` 폴더에 두어도 괜찮습니다. 더 정리된 구조를 위해 `app/ambient/` 폴더에 두는 것도 좋은 방법입니다.
+
+타이핑이 없는 패키지인 경우, 해당 패키지를 미타입으로 선언하거나 직접 타이핑을 작성할 수 있습니다. 이러한 타이핑을 위한 좋은 위치는 `shared/lib` 폴더 내의 `shared/lib/untyped-packages` 폴더입니다. 이 폴더에 `%LIBRARY_NAME%.d.ts` 파일을 생성하고 필요한 타입을 선언합니다
+
+```ts title="shared/lib/untyped-packages/use-react-screenshot.d.ts"
+// 이 라이브러리는 타입 정의가 없으며 작성하는 것을 생략했습니다.
+declare module "use-react-screenshot";
+```
+
+## 타입 자동 생성
+
+외부 소스로부터 타입을 생성하는 일은 흔히 발생합니다. 예를 들어, OpenAPI 스키마로부터 백엔드 타입을 생성하는 경우가 있습니다.
+이러한 타입을 위한 전용 위치를 코드베이스에 만드는 것이 좋습니다. 예를 들어 `shared/api/openapi`와 같은 위치가 적합합니다. 이상적으로는 이러한 파일이 무엇인지, 어떻게 재생성하는지 등을 설명하는 README 파일도 포함하는 것이 좋습니다.
+
+[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers
+[ext-type-fest]: https://github.com/sindresorhus/type-fest
+[ext-zod]: https://zod.dev
+[ext-vite]: https://vitejs.dev
+[ext-ts-reset]: https://www.totaltypescript.com/ts-reset
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/index.mdx
new file mode 100644
index 0000000000..6d7a02ccda
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/index.mdx
@@ -0,0 +1,50 @@
+---
+hide_table_of_contents: true
+pagination_prev: get-started/index
+---
+
+# 🎯 Guides
+
+PRACTICE-ORIENTED
+
+
+Feature-Sliced Design(FSD)의 적용을 위한 종합 가이드입니다. 구체적인 예시, 마이그레이션 전략, 그리고 FSD 코드에서 발견할 수 있는 흔한 설계상의 문제들을 다룹니다. FSD를 프로젝트에 도입하거나 기존 구조를 개선하고자 할 때 참고하기 좋은 리소스입니다.
+
+
+## Main
+
+import NavCard from "@site/src/shared/ui/nav-card/tmpl.mdx"
+import { ToolOutlined, ImportOutlined, BugOutlined, FunctionOutlined } from "@ant-design/icons";
+
+
+
+
+
+
+
+
+
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx
new file mode 100644
index 0000000000..1a31af2b7e
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-nextjs.mdx
@@ -0,0 +1,117 @@
+---
+sidebar_position: 10
+---
+# NextJS와 함께 사용하기
+
+NextJS에서도 FSD(Feature-Sliced Design) 아키텍처를 구현할 수 있지만, 두 가지 점에서 NextJS의 프로젝트 구조 요구사항과 FSD 구조 간에 충돌이 발생합니다:
+
+- `pages` 폴더와의 라우팅 방식 차이
+- NextJS에서 `app` 폴더의 충돌 문제 또는 부재
+
+## FSD와 NextJS의 `페이지` 레이어 간 충돌 {#pages-conflict}
+
+NextJS는 애플리케이션 라우트를 정의하기 위해 `pages` 폴더를 사용하며, `pages` 폴더 내의 파일이 URL과 매핑되도록 설정합니다.
+하지만 이 방식은 FSD(Folder Slice Design) `개념에 맞지는 않습니다`. 특히, NextJS의 라우팅 방식으로는 FSD의 슬라이스 구조를 평평하게 유지하기 어려운 점이 있습니다.
+
+### NextJS의 `pages` 폴더를 프로젝트 루트 폴더로 이동하기 (권장)
+
+프로젝트 루트에 `pages` 폴더를 배치하고, FSD 구조에 맞춘 페이지들을 NextJS의 `pages` 폴더로 옮깁니다.
+이렇게 하면 `src` 폴더 내에서 FSD 구조를 유지할 수 있습니다.
+
+```sh
+├── pages # NextJS 페이지 폴더
+├── src
+│ ├── app
+│ ├── entities
+│ ├── features
+│ ├── pages # FSD 페이지 폴더
+│ ├── shared
+│ ├── widgets
+```
+
+### FSD 구조 내 `pages` 폴더 이름 변경하기
+
+다른 방법으로는 FSD 구조 내에서 `pages` 폴더의 이름을 변경하여 NextJS의 `pages` 폴더와 충돌을 피할 수도 있습니다.
+예를 들어, `pages` 폴더를 `views`로 이름을 변경하면 `src` 폴더 내의 FSD 구조를 유지하면서도 NextJS의 요구 사항과 충돌하지 않게 됩니다.
+
+```sh
+├── app
+├── entities
+├── features
+├── pages # NextJS 페이지 폴더
+├── views # 이름이 변경된 FSD 페이지 폴더
+├── shared
+├── widgets
+```
+
+이름을 변경하는 경우, 이를 프로젝트의 README나 내부 문서에 명확히 기록하여 변경 사항이 잘 전달되도록 하는 것이 좋습니다. 이러한 변경은 ["프로젝트 지식"][project-knowledge]의 일부로 문서화하는 것이 중요합니다.
+
+## NextJS에서 `app` 폴더 부재 문제 {#app-absence}
+
+NextJS 13버전 이하에서는 명시적인 `app` 폴더가 없으며,
+대신 `_app.tsx` 파일이 모든 페이지를 감싸는 컴포넌트 역할을 합니다.
+
+### `pages/_app.tsx` 파일에 app 기능 가져오기
+
+NextJS 구조에서 `app` 폴더가 없는 문제를 해결하려면, `app` 폴더 내에 `App` 컴포넌트를 생성하고, 이를 `pages/_app.tsx`에 가져와 NextJS가 사용할 수 있도록 설정하면 됩니다. 예를 들어:
+
+```tsx
+// app/providers/index.tsx
+
+const App = ({ Component, pageProps }: AppProps) => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default App;
+```
+
+그 다음 `pages/_app.tsx` 파일에서 `App` 컴포넌트와 프로젝트 전역 스타일을 다음과 같이 가져올 수 있습니다:
+
+```tsx
+// pages/_app.tsx
+
+import 'app/styles/index.scss'
+
+export { default } from 'app/providers';
+```
+
+## App Router 사용하기 {#app-router}
+
+NextJS 13.4 버전에서는 App Router가 안정화되었습니다. App Router를 사용하면 `pages` 폴더 대신 `app` 폴더를 통해 라우팅을 처리할 수 있습니다.
+FSD 원칙을 준수하기 위해, NextJS의 `app` 폴더도 `pages` 폴더와의 충돌 문제를 해결한 것과 동일한 방식으로 다루어야 합니다.
+
+이를 위해 NextJS의 `app` 폴더를 프로젝트 루트로 이동하고, FSD 페이지들을 `app` 폴더로 옮기는 방식을 사용합니다.
+이렇게 하면 `src` 폴더 내에서 FSD 프로젝트 구조를 유지할 수 있습니다.
+또한, App Router와 Pages Router가 호환되므로 `pages` 폴더를 프로젝트 루트에 추가하는 것이 필요합니다.
+
+```
+├── app # NextJS app 폴더
+├── pages # NextJS pages 폴더
+│ ├── README.md # 해당 폴더의 목적과 역할에 대한 설명
+├── src
+│ ├── app # FSD app 폴더
+│ ├── entities
+│ ├── features
+│ ├── pages # FSD pages 폴더
+│ ├── shared
+│ ├── widgets
+```
+
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)][ext-app-router-stackblitz]
+
+## 관련 항목 {#see-also}
+
+- [(스레드) NextJS의 pages 디렉토리에 대한 토론](https://t.me/feature_sliced/3623)
+
+[project-knowledge]: /docs/about/understanding/knowledge-types
+[ext-app-router-stackblitz]: https://stackblitz.com/edit/stackblitz-starters-aiez55?file=README.md
+
+
diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx
new file mode 100644
index 0000000000..0c3e01e3cf
--- /dev/null
+++ b/i18n/kr/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx
@@ -0,0 +1,425 @@
+---
+sidebar_position: 10
+---
+# React Query와 함께 사용하기
+
+## “키를 어디에 두어야 하는가” 문제
+
+### 해결책 — 엔티티별로 분리하기
+
+프로젝트가 이미 엔티티 단위로 구성되어 있으며, 각 요청이 단일 엔티티에 해당한다면, 엔티티별로 코드를 구성하는 것이 좋습니다. 예를 들어, 다음과 같은 디렉토리 구조를 사용할 수 있습니다:
+
+```sh
+└── src/ #
+ ├── app/ #
+ | ... #
+ ├── pages/ #
+ | ... #
+ ├── entities/ #
+ | ├── {entity}/ #
+ | ... └── api/ #
+ | ├── `{entity}.query` # 쿼리 키와 함수
+ | ├── `get-{entity}` # 엔티티 조회 함수
+ | ├── `create-{entity}` # 엔티티 생성 함수
+ | ├── `update-{entity}` # 엔티티 업데이트 함수
+ | ├── `delete-{entity}` # 엔티티 삭제 함수
+ | ... #
+ | #
+ ├── features/ #
+ | ... #
+ ├── widgets/ #
+ | ... #
+ └── shared/ #
+ ... #
+```
+
+만약 엔티티 간에 연결이 필요한 경우 (예: Country 엔티티에 City 엔티티 필드가 포함되는 경우), [교차 가져오기를 위한 공개 API][public-api-for-cross-imports]을 사용하거나 대안으로 아래의 구조를 고려할 수 있습니다.
+
+### 대안 방안 — shared에 유지하기
+
+엔티티별 분리가 적절하지 않은 경우, 다음과 같은 구조를 사용할 수 있습니다:
+
+```sh
+└── src/ #
+ ... #
+ └── shared/ #
+ ├── api/ #
+ ... ├── `queries` # 쿼리 팩토리들
+ | ├── `document.ts` #
+ | ├── `background-jobs.ts` #
+ | ... #
+ └── index.ts #
+```
+
+이후 `@/shared/api/index.ts`에서 다음과 같이 사용합니다:
+
+```ts title="@/shared/api/index.ts"
+export { documentQueries } from "./queries/document";
+```
+
+## "mutation 위치 설정" 문제
+
+쿼리와 mutation을 같은 위치에 두는 것은 권장되지 않습니다. 다음 두 가지 옵션이 있습니다:
+
+### 1. 사용 위치 근처의 `api` 디렉토리에서 커스텀 훅 정의하기
+
+```tsx title="@/features/update-post/api/use-update-title.ts"
+export const useUpdateTitle = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationFn: ({ id, newTitle }) =>
+ apiClient
+ .patch(`/posts/${id}`, { title: newTitle })
+ .then((data) => console.log(data)),
+
+ onSuccess: (newPost) => {
+ queryClient.setQueryData(postsQueries.ids(id), newPost);
+ },
+ });
+};
+```
+
+### 2. 공용 또는 엔티티에서 mutation 함수를 정의하고, 컴포넌트에서 `useMutation`을 직접 사용하기
+
+```tsx
+const { mutateAsync, isPending } = useMutation({
+ mutationFn: postApi.createPost,
+});
+```
+
+```tsx title="@/pages/post-create/ui/post-create-page.tsx"
+export const CreatePost = () => {
+ const { classes } = useStyles();
+ const [title, setTitle] = useState("");
+
+ const { mutate, isPending } = useMutation({
+ mutationFn: postApi.createPost,
+ });
+
+ const handleChange = (e: ChangeEvent) =>
+ setTitle(e.target.value);
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ mutate({ title, userId: DEFAULT_USER_ID });
+ };
+
+ return (
+
+ );
+};
+```
+
+## 요청의 조직화
+
+### 쿼리 팩토리
+
+쿼리 팩토리는 쿼리 키 목록을 반환하는 함수를 포함한 객체입니다. 사용 방법은 다음과 같습니다:
+
+```ts
+const keyFactory = {
+ all: () => ["entity"],
+ lists: () => [...postQueries.all(), "list"],
+};
+```
+
+:::info
+`queryOptions`는 react-query@v5의 내장 유틸리티입니다 (선택 사항)
+
+```ts
+queryOptions({
+ queryKey,
+ ...options,
+});
+```
+
+더 큰 타입 안정성, react-query의 향후 버전과의 호환성, 함수 및 쿼리 키에 대한 쉬운 액세스를 위해, "@tanstack/react-query"의 내장 queryOptions 함수를 사용할 수 있습니다 [(자세한 내용은 여기)](https://tkdodo.eu/blog/the-query-options-api#queryoptions).
+
+:::
+
+### 1. 쿼리 팩토리 생성 예시
+
+```tsx title="@/entities/post/api/post.queries.ts"
+import { keepPreviousData, queryOptions } from "@tanstack/react-query";
+import { getPosts } from "./get-posts";
+import { getDetailPost } from "./get-detail-post";
+import { PostDetailQuery } from "./query/post.query";
+
+export const postQueries = {
+ all: () => ["posts"],
+
+ lists: () => [...postQueries.all(), "list"],
+ list: (page: number, limit: number) =>
+ queryOptions({
+ queryKey: [...postQueries.lists(), page, limit],
+ queryFn: () => getPosts(page, limit),
+ placeholderData: keepPreviousData,
+ }),
+
+ details: () => [...postQueries.all(), "detail"],
+ detail: (query?: PostDetailQuery) =>
+ queryOptions({
+ queryKey: [...postQueries.details(), query?.id],
+ queryFn: () => getDetailPost({ id: query?.id }),
+ staleTime: 5000,
+ }),
+};
+```
+
+### 2. 애플리케이션 코드에서의 쿼리 팩토리 사용 예시
+```tsx
+import { useParams } from "react-router-dom";
+import { postApi } from "@/entities/post";
+import { useQuery } from "@tanstack/react-query";
+
+type Params = {
+ postId: string;
+};
+
+export const PostPage = () => {
+ const { postId } = useParams();
+ const id = parseInt(postId || "");
+ const {
+ data: post,
+ error,
+ isLoading,
+ isError,
+ } = useQuery(postApi.postQueries.detail({ id }));
+
+ if (isLoading) {
+ return
+ );
+};
+```
+
+### 쿼리 팩토리 사용의 장점
+- **요청 구조화**: 팩토리를 통해 모든 API 요청을 한 곳에 조직화하여 코드의 가독성과 유지보수성을 높입니다.
+- **쿼리 및 키에 대한 편리한 접근**: 다양한 유형의 쿼리와 해당 키에 쉽게 접근할 수 있는 메서드를 제공합니다.
+- **쿼리 재호출 용이성**: 애플리케이션의 여러 부분에서 쿼리 키를 변경할 필요 없이 쉽게 재호출할 수 있습니다.
+
+## 페이지네이션
+이 섹션에서는 페이지네이션을 사용하여 게시물 엔티티를 가져오는 API 요청을 수행하는 `getPosts` 함수의 예를 소개합니다.
+
+### 1. `getPosts` 함수 생성하기
+getPosts 함수는 `api` 세그먼트의 `get-posts.ts` 파일에 있습니다.
+
+```tsx title="@/pages/post-feed/api/get-posts.ts"
+import { apiClient } from "@/shared/api/base";
+
+import { PostWithPaginationDto } from "./dto/post-with-pagination.dto";
+import { PostQuery } from "./query/post.query";
+import { mapPost } from "./mapper/map-post";
+import { PostWithPagination } from "../model/post-with-pagination";
+
+const calculatePostPage = (totalCount: number, limit: number) =>
+ Math.floor(totalCount / limit);
+
+export const getPosts = async (
+ page: number,
+ limit: number,
+): Promise => {
+ const skip = page * limit;
+ const query: PostQuery = { skip, limit };
+ const result = await apiClient.get("/posts", query);
+
+ return {
+ posts: result.posts.map((post) => mapPost(post)),
+ limit: result.limit,
+ skip: result.skip,
+ total: result.total,
+ totalPages: calculatePostPage(result.total, limit),
+ };
+};
+```
+
+### 2. 페이지네이션을 위한 쿼리 팩토리
+`postQueries` 쿼리 팩토리는 특정 페이지와 제한에 맞춰 게시물 목록을 요청하는 등 게시물 관련 다양한 쿼리 옵션을 정의합니다.
+
+```tsx
+import { keepPreviousData, queryOptions } from "@tanstack/react-query";
+import { getPosts } from "./get-posts";
+
+export const postQueries = {
+ all: () => ["posts"],
+ lists: () => [...postQueries.all(), "list"],
+ list: (page: number, limit: number) =>
+ queryOptions({
+ queryKey: [...postQueries.lists(), page, limit],
+ queryFn: () => getPosts(page, limit),
+ placeholderData: keepPreviousData,
+ }),
+};
+```
+
+
+### 3. 애플리케이션 코드에서의 사용
+
+```tsx title="@/pages/home/ui/index.tsx"
+export const HomePage = () => {
+ const itemsOnScreen = DEFAULT_ITEMS_ON_SCREEN;
+ const [page, setPage] = usePageParam(DEFAULT_PAGE);
+ const { data, isFetching, isLoading } = useQuery(
+ postApi.postQueries.list(page, itemsOnScreen),
+ );
+ return (
+ <>
+ setPage(page)}
+ page={page}
+ count={data?.totalPages}
+ variant="outlined"
+ color="primary"
+ />
+
+ >
+ );
+};
+```
+:::note
+예시는 단순화된 버전이며, 전체 코드는 [GitHub](https://github.com/ruslan4432013/fsd-react-query-example)에서 확인할 수 있습니다.
+:::
+
+## 쿼리 관리를 위한 `QueryProvider`
+이 가이드에서는 `QueryProvider`를 어떻게 구성하는지 살펴봅니다.
+
+### 1. `QueryProvider` 생성하기
+`query-provider.tsx` 파일은 `@/app/providers/query-provider.tsx` 경로에 있습니다.
+
+```tsx title="@/app/providers/query-provider.tsx"
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+import { ReactNode } from "react";
+
+type Props = {
+ children: ReactNode;
+ client: QueryClient;
+};
+
+export const QueryProvider = ({ client, children }: Props) => {
+ return (
+
+ {children}
+
+
+ );
+};
+```
+
+### 2. `QueryClient` 생성하기
+`QueryClient`는 API 요청을 관리하는 인스턴스입니다. `query-client.ts` 파일은 `@/shared/api/query-client.ts`에 속해 있으며, 쿼리 캐싱을 위해 특정 설정으로 `QueryClient`를 생성합니다.
+
+```tsx title="@/shared/api/query-client.ts"
+import { QueryClient } from "@tanstack/react-query";
+
+export const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 5 * 60 * 1000,
+ gcTime: 5 * 60 * 1000,
+ },
+ },
+});
+```
+
+## 코드 생성
+
+API 코드를 생성해주는 도구들이 있지만, 이러한 방식은 위의 예제 처럼 직접 코드를 작성하는 방법보다 유연성이 부족할 수 있습니다. 그러나 Swagger 파일이 잘 구성되어 있고 이러한 자동 생성 도구를 사용하는 경우, 생성된 코드를 `@/shared/api` 디렉토리에 두어 관리하는 것이 효율적일 수 있습니다.
+
+
+## React Query를 조직화하기 위한 추가 조언
+### API 클라이언트
+
+공유 레이어에서 커스텀 API 클라이언트 클래스를 사용하면, 프로젝트 내 API 작업을 일관성 있게 관리할 수 있습니다. 이를 통해 로깅, 헤더 설정, 데이터 전송 형식(JSON 또는 XML 등)을 한 곳에서 관리할 수 있게 됩니다. 또한 이 접근 방식은 API와의 상호작용에 대한 변경 사항을 쉽게 반영할 수 있게 하여, 프로젝트의 유지보수성과 개발 편의성을 크게 향상시킵니다.
+
+
+```tsx title="@/shared/api/api-client.ts"
+import { API_URL } from "@/shared/config";
+
+export class ApiClient {
+ private baseUrl: string;
+
+ constructor(url: string) {
+ this.baseUrl = url;
+ }
+
+ async handleResponse(response: Response): Promise {
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ try {
+ return await response.json();
+ } catch (error) {
+ throw new Error("Error parsing JSON response");
+ }
+ }
+
+ public async get(
+ endpoint: string,
+ queryParams?: Record,
+ ): Promise {
+ const url = new URL(endpoint, this.baseUrl);
+
+ if (queryParams) {
+ Object.entries(queryParams).forEach(([key, value]) => {
+ url.searchParams.append(key, value.toString());
+ });
+ }
+ const response = await fetch(url.toString(), {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ return this.handleResponse(response);
+ }
+
+ public async post>(
+ endpoint: string,
+ body: TData,
+ ): Promise {
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ });
+
+ return this.handleResponse(response);
+ }
+}
+
+export const apiClient = new ApiClient(API_URL);
+```
+
+## 참고 자료 {#see-also}
+
+- [(GitHub) 샘플 프로젝트](https://github.com/ruslan4432013/fsd-react-query-example)
+- [(CodeSandbox) 샘플 프로젝트](https://codesandbox.io/p/github/ruslan4432013/fsd-react-query-example/main)
+- [쿼리 팩토리에 대하여](https://tkdodo.eu/blog/the-query-options-api)
+
+[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current.json b/i18n/ru/docusaurus-plugin-content-docs/current.json
index 6487d3a7d4..0ecd0d7164 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current.json
+++ b/i18n/ru/docusaurus-plugin-content-docs/current.json
@@ -1,6 +1,6 @@
{
"version.label": {
- "message": "v2.0.0 🍰",
+ "message": "v2.1",
"description": "The label for version current"
},
"sidebar.getstartedSidebar.category.🚀 Get Started": {
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md
index f5ea8d3154..1a51f0c1d8 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md
@@ -13,7 +13,7 @@ pagination_next: guides/index
### Существует ли тулкит или линтер? {#is-there-a-toolkit-or-a-linter}
-Есть официальный конфиг для ESLint — [@feature-sliced/eslint-config][eslint-config-official], и плагин для ESLint — [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti], созданный участником сообщества Александром Белоусом. Мы будем рады вашим вкладам в эти проекты или созданию своих!
+Да! У нас есть линтер [Steiger][ext-steiger] для проверки архитектуры вашего проекта и [генераторы папок][ext-tools] через CLI или IDE.
### Где хранить layout/template страниц? {#where-to-store-the-layouttemplate-of-pages}
@@ -58,10 +58,10 @@ _Entity_ — это понятие из реальной жизни, с кото
Ответили [здесь](/docs/guides/examples/auth)
+[ext-steiger]: https://github.com/feature-sliced/steiger
+[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools
[import-rule-layers]: /docs/reference/layers#import-rule-on-layers
[reference-entities]: /docs/reference/layers#entities
-[eslint-config-official]: https://github.com/feature-sliced/eslint-config
-[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced
[motivation]: /docs/about/motivation
[telegram]: https://t.me/feature_sliced
[discord]: https://discord.gg/S8MzWTUsmp
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.mdx
index 776f75a0b1..2d92c60954 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.mdx
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/overview.mdx
@@ -65,15 +65,15 @@ FSD можно внедрять в проектах и командах любо
Слои стандартизированы во всех проектах FSD. Вам не обязательно использовать все слои, но их названия важны. На данный момент их семь (сверху вниз):
-1. App\* — всё, благодаря чему приложение запускается — роутинг, точки входа, глобальные стили, провайдеры и т. д.
-2. Processes (процессы, устаревший) — сложные межстраничные сценарии.
-3. Pages (страницы) — полные страницы или большие части страницы при вложенном роутинге.
-4. Widgets (виджеты) — большие самодостаточные куски функциональности или интерфейса, обычно реализующие целый пользовательский сценарий.
-5. Features (фичи) — _повторно используемые_ реализации целых фич продукта, то есть действий, приносящих бизнес-ценность пользователю.
-6. Entities (сущности) — бизнес-сущности, с которыми работает проект, например `user` или `product`.
-7. Shared\* — переиспользуемый код, особенно когда она отделена от специфики проекта/бизнеса, хотя это не обязательно.
+1. **App\*** — всё, благодаря чему приложение запускается — роутинг, точки входа, глобальные стили, провайдеры и т. д.
+2. **Processes** (процессы, устаревший) — сложные межстраничные сценарии.
+3. **Pages** (страницы) — полные страницы или большие части страницы при вложенном роутинге.
+4. **Widgets** (виджеты) — большие самодостаточные куски функциональности или интерфейса, обычно реализующие целый пользовательский сценарий.
+5. **Features** (фичи) — _повторно используемые_ реализации целых фич продукта, то есть действий, приносящих бизнес-ценность пользователю.
+6. **Entities** (сущности) — бизнес-сущности, с которыми работает проект, например `user` или `product`.
+7. **Shared\*** — переиспользуемый код, особенно когда он отделён от специфики проекта/бизнеса, хотя это не обязательно.
-_\* — эти слои, App и Shared, в отличие от других слоев, не имеют слайсов и состоят из сегментов напрямую._
+_\* — эти слои, **App** и **Shared**, в отличие от других слоев, не имеют слайсов и состоят из сегментов напрямую._
Фишка слоев в том, что модули на одном слое могут знать только о модулях со слоев строго ниже, и как следствие, импортировать только с них.
@@ -131,7 +131,7 @@ _\* — эти слои, App и Shared, в отличие от других сл
[tutorial]: /docs/get-started/tutorial
[examples]: /examples
-[migration]: /docs/guides/migration/from-legacy
+[migration]: /docs/guides/migration/from-custom
[ext-steiger]: https://github.com/feature-sliced/steiger
[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools
[ext-telegram]: https://t.me/feature_sliced
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md
index 3bd40159a6..6638f7d837 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md
@@ -39,7 +39,7 @@ sidebar_position: 2
Ключевое отличие Feature-Sliced Design от произвольной структуры кода заключается в том, что страницы не могут зависеть друг от друга. То есть одна страница не может импортировать код с другой страницы. Это связано с **правилом импорта для слоёв**:
-*Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже.*
+*Модуль (файл) в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже.*
В этом случае страница является слайсом, поэтому модули (файлы) внутри этой страницы могут импортировать код только из слоев ниже, а не из других страниц.
@@ -696,7 +696,7 @@ export function FeedPage() {
### Аутентификация {#authentication}
-Аутентификация включает в себя две страницы — одну для входа в систему и другую для регистрации. Они, в основном, очень схожие, поэтому имеет смысл держать их в одном сегменте, `sign-in`, чтобы при необходимости можно было переиспользовать код.
+Аутентификация включает в себя две страницы — одну для входа в систему и другую для регистрации. Они, в основном, очень схожие, поэтому имеет смысл держать их в одном слайсе, `sign-in`, чтобы при необходимости можно было переиспользовать код.
Создайте `RegisterPage.tsx` в сегменте `ui` в `pages/sign-in` со следующим содержимым:
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md
index 1b09d12279..1807b69aed 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md
@@ -189,7 +189,7 @@ export function login({ email, password }: { email: string, password: string })
Поскольку API-клиент обычно размещается в `shared/api` или распределяется между сущностями, главной проблемой этого подхода является обеспечение доступа к токену для других запросов, не нарушая при этом [правило импортов для слоёв][import-rule-on-layers]:
-> Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже.
+> Модуль (файл) в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже.
Есть несколько решений этой проблемы:
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.md
index c00ae7fe90..5108e959e3 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.md
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/types.md
@@ -307,7 +307,7 @@ export const slice = createSlice({
extraReducers: (builder) => {
builder.addCase(fetchSong.fulfilled, (state, action) => {
// И здесь обрабатываем тот же ответ с бэкенда, добавляя исполнителей
- usersAdapter.upsertMany(state, action.payload.users)
+ artistAdapter.upsertMany(state, action.payload.artists)
})
},
})
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/index.mdx
index 74b124da46..f720736132 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/index.mdx
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/index.mdx
@@ -25,10 +25,10 @@ import { ToolOutlined, ImportOutlined, BugOutlined, FunctionOutlined } from "@an
/>
+ 📁 src
+
+
+
+ 📁 actions
+
+
📁 product
+
📁 order
+
+
+
+
📁 api
+
📁 components
+
📁 containers
+
📁 constants
+
📁 i18n
+
📁 modules
+
📁 helpers
+
+
+ 📁 routes
+
+
📁 products.jsx
+
📄 products.[id].jsx
+
+
+
+
📁 utils
+
📁 reducers
+
📁 selectors
+
📁 styles
+
📄 App.jsx
+
📄 index.js
+
+
+
+## Перед началом {#before-you-start}
+
+Самый важный вопрос, который нужно задать своей команде при рассмотрении перехода на Feature-Sliced Design, — _действительно ли вам это нужно?_ Мы любим Feature-Sliced Design, но даже мы признаем, что некоторые проекты прекрасно обойдутся и без него.
+
+Вот несколько причин, по которым стоит рассмотреть переход:
+
+1. Новые члены команды жалуются, что сложно достичь продуктивного уровня
+2. Внесение изменений в одну часть кода **часто** приводит к тому, что ломается другая несвязанная часть
+3. Добавление новой функциональности затруднено из-за огромного количества вещей, о которых нужно думать
+
+**Избегайте перехода на FSD против воли ваших коллег**, даже если вы являетесь тимлидом.
+Сначала убедите своих коллег в том, что преимущества перевешивают стоимость миграции и стоимость изучения новой архитектуры вместо установленной.
+
+Также имейте в виду, что любые изменения в архитектуре незаметны для руководства в моменте. Убедитесь, что они поддерживают переход, прежде чем начинать, и объясните им, как этот переход может быть полезен для проекта.
+
+:::tip
+
+Если вам нужна помощь в убеждении менеджера проекта в том, что FSD вам полезен, вот несколько идей:
+
+1. Миграция на FSD может происходить постепенно, поэтому она не остановит разработку новых функций
+2. Хорошая архитектура может значительно сократить время, которое потребуется новым разработчикам для достижения производительности
+3. FSD — это документированная архитектура, поэтому команде не нужно постоянно тратить время на поддержание собственной документации
+
+:::
+
+---
+
+Если вы всё-таки приняли решение начать миграцию, то первое, что вам следует сделать, — настроить алиас для `📁 src`. Это будет полезно позже, чтоб ссылаться на папки верхнего уровня. Далее в тексте мы будем считать `@` псевдонимом для `./src`.
+
+## Шаг 1. Разделите код по страницам {#divide-code-by-pages}
+
+Большинство кастомных архитектур уже имеют разделение по страницам, независимо от размера логики. Если у вас уже есть `📁 pages`, вы можете пропустить этот шаг.
+
+Если у вас есть только `📁 routes`, создайте `📁 pages` и попробуйте переместить как можно больше кода компонентов из `📁 routes`. Идеально, если у вас будет маленький файл роута и больший файл страницы. При перемещении кода создайте папку для каждой страницы и добавьте в нее индекс-файл:
+
+:::note
+
+Пока что ваши страницы могут импортировать друг из друга, это нормально. Позже будет отдельный шаг для устранения этих зависимостей, но сейчас сосредоточьтесь на установлении явного разделения по страницам.
+
+:::
+
+Файл роута:
+
+```js title="src/routes/products.[id].js"
+export { ProductPage as default } from "@/pages/product"
+```
+
+Индекс-файл страницы:
+
+```js title="src/pages/product/index.js"
+export { ProductPage } from "./ProductPage.jsx"
+```
+
+Файл с компонентом страницы:
+
+```jsx title="src/pages/product/ProductPage.jsx"
+export function ProductPage(props) {
+ return ;
+}
+```
+
+## Шаг 2. Отделите все остальное от страниц {#separate-everything-else-from-pages}
+
+Создайте папку `📁 src/shared` и переместите туда все, что не импортируется из `📁 pages` или `📁 routes`. Создайте папку `📁 src/app` и переместите туда все, что импортирует страницы или роуты, включая сами роуты.
+
+Помните, что у слоя Shared нет слайсов, поэтому сегменты могут импортировать друг из друга.
+
+В итоге у вас должна получиться структура файлов, похожая на эту:
+
+
+ 📁 src
+
+
+
+ 📁 app
+
+
+
+ 📁 routes
+
+
📄 products.jsx
+
📄 products.[id].jsx
+
+
+
+
📄 App.jsx
+
📄 index.js
+
+
+
+
+
+ 📁 pages
+
+
+
+ 📁 product
+
+
+
+ 📁 ui
+
+
📄 ProductPage.jsx
+
+
+
+
📄 index.js
+
+
+
+
📁 catalog
+
+
+
+
+
+ 📁 shared
+
+
📁 actions
+
📁 api
+
📁 components
+
📁 containers
+
📁 constants
+
📁 i18n
+
📁 modules
+
📁 helpers
+
📁 utils
+
📁 reducers
+
📁 selectors
+
📁 styles
+
+
+
+
+
+
+## Шаг 3. Устраните кросс-импорты между страницами {#tackle-cross-imports-between-pages}
+
+Найдите все случаи, когда одна страница импортирует что-то из другой, и сделайте одно из двух:
+
+1. Скопируйте код, который импортируется, в зависимую страницу, чтобы убрать зависимость
+2. Переместите код в соответствующий сегмент в Shared:
+ - если это часть UI-кита, переместите в `📁 shared/ui`;
+ - если это константа конфигурации, переместите в `📁 shared/config`;
+ - если это взаимодействие с бэкендом, переместите в `📁 shared/api`.
+
+:::note
+
+**Копирование само по себе не является архитектурной проблемой**, на самом деле иногда даже правильнее продублировать что-то, чем абстрагировать в новый переиспользуемый модуль. Дело в том, что иногда общие части страниц начинают расходиться, и в этих случаях вам не нужно, чтобы эти зависимости мешались.
+
+Однако существует смысл в принципе DRY ("don't repeat yourself" — "не повторяйтесь"), поэтому убедитесь, что вы не копируете бизнес-логику. В противном случае вам придется держать в голове, что баги нужно исправлять в нескольких местах одновременно.
+
+:::
+
+## Шаг 4. Разберите слой Shared {#unpack-shared-layer}
+
+На данном этапе у вас может быть много всего в слое Shared, и в целом, следует избегать таких ситуаций. Причина этому в том, что слой Shared может быть зависимостью для любого другого слоя в вашем коде, поэтому внесение изменений в этот код автоматически более чревато непредвиденными последствиями.
+
+Найдите все объекты, которые используются только на одной странице, и переместите их в слайс этой страницы. И да, _это относится и к экшнам (actions), редьюсерам (reducers) и селекторам (selectors)_. Нет никакой пользы в группировке всех экшнов вместе, но есть польза в том, чтобы поместить актуальные экшны рядом с их местом использования.
+
+В итоге у вас должна получиться структура файлов, похожая на эту:
+
+
+ 📁 src
+
+
📁 app (unchanged)
+
+
+ 📁 pages
+
+
+
+ 📁 product
+
+
📁 actions
+
📁 reducers
+
📁 selectors
+
+
+ 📁 ui
+
+
📄 Component.jsx
+
📄 Container.jsx
+
📄 ProductPage.jsx
+
+
+
+
📄 index.js
+
+
+
+
📁 catalog
+
+
+
+
+
+ 📁 shared (only objects that are reused)
+
+
📁 actions
+
📁 api
+
📁 components
+
📁 containers
+
📁 constants
+
📁 i18n
+
📁 modules
+
📁 helpers
+
📁 utils
+
📁 reducers
+
📁 selectors
+
📁 styles
+
+
+
+
+
+
+## Шаг 5. Распределите код по техническому назначению {#organize-by-technical-purpose}
+
+В FSD разделение по техническому назначению происходит с помощью _сегментов_. Существует несколько часто встречающихся сегментов:
+
+- `ui` — всё, что связано с отображением интерфейса: компоненты UI, форматирование дат, стили и т. д.
+- `api` — взаимодействие с бэкендом: функции запросов, типы данных, мапперы и т. д.
+- `model` — модель данных: схемы, интерфейсы, хранилища и бизнес-логика.
+- `lib` — библиотечный код, который нужен другим модулям на этом слайсе.
+- `config` — файлы конфигурации и фиче-флаги.
+
+Вы можете создавать свои собственные сегменты, если это необходимо. Убедитесь, что не создаете сегменты, которые группируют код по тому, чем он является, например, `components`, `actions`, `types`, `utils`. Вместо этого группируйте код по тому, для чего он предназначен.
+
+Перераспределите код ваших страниц по сегментам. У вас уже должен быть сегмент `ui`, теперь пришло время создать другие сегменты, например, `model` для ваших экшнов, редьюсеров и селекторов, или `api` для ваших thunk-ов и мутаций.
+
+Также перераспределите слой Shared, чтобы удалить следующие папки:
+- `📁 components`, `📁 containers` — большинство из их содержимого должно стать `📁 shared/ui`;
+- `📁 helpers`, `📁 utils` — если остались какие-то повторно используемые хелперы, сгруппируйте их по назначению, например, даты или преобразования типов, и переместите эти группы в `📁 shared/lib`;
+- `📁 constants` — так же сгруппируйте по назначению и переместите в `📁 shared/config`.
+
+## Шаги по желанию {#optional-steps}
+
+### Шаг 6. Создайте сущности/фичи ёмкостью из Redux-слайсов, которые используются на нескольких страницах {#form-entities-features-from-redux}
+
+Обычно эти переиспользуемые Redux-слайсы будут описывать что-то, что имеет отношение к бизнесу, например, продукты или пользователи, поэтому их можно переместить в слой Entities, одна сущность на одну папку. Если Redux-слайс скорее связан с действием, которое ваши пользователи хотят совершить в вашем приложении, например, комментарии, то его можно переместить в слой Features.
+
+Сущности и фичи должны быть независимы друг от друга. Если ваша бизнес-область содержит встроенные связи между сущностями, обратитесь к [руководству по бизнес-сущностям][business-entities-cross-relations] за советом по организации этих связей.
+
+API-функции, связанные с этими слайсами, могут остаться в `📁 shared/api`.
+
+### Шаг 7. Проведите рефакторинг modules {#refactor-your-modules}
+
+Папка `📁 modules` обычно используется для бизнес-логики, поэтому она уже довольно похожа по своей природе на слой Features из FSD. Некоторые модули могут также описывать большие части пользовательского интерфейса, например, шапку приложения. В этом случае их можно переместить в слой Widgets.
+
+### Шаг 8. Сформируйте чистый фундамент UI в `shared/ui` {#form-clean-ui-foundation}
+
+`📁 shared/ui`, в идеале, должен содержать набор UI-элементов, в которых нет бизнес-логики. Они также должны быть очень переиспользуемыми.
+
+Проведите рефакторинг UI-компонентов, которые раньше находились в `📁 components` и `📁 containers`, чтобы отделить бизнес-логику. Переместите эту бизнес-логику в верхние слои. Если она не используется в слишком многих местах, вы даже можете рассмотреть копирование как вариант.
+
+## See also {#see-also}
+
+- [(Доклад) Ilya Klimov — Крысиные бега бесконечного рефакторинга: как не дать техническому долгу убить мотивацию и продукт](https://youtu.be/aOiJ3k2UvO4)
+
+[ext-steiger]: https://github.com/feature-sliced/steiger
+[business-entities-cross-relations]: /docs/guides/examples/types#business-entities-and-their-cross-references
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx
deleted file mode 100644
index 7bc5252981..0000000000
--- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-legacy.mdx
+++ /dev/null
@@ -1,121 +0,0 @@
----
-sidebar_position: 3
-sidebar_class_name: sidebar-item--wip
----
-
-import WIP from '@site/src/shared/ui/wip/tmpl.mdx'
-
-# Миграция с legacy
-
-
-
-> В статье агрегируется опыт нескольких компаний и проектов по переезду на Feature-Sliced Design с разными изначальными условиями
-
-## Зачем? {#why}
-
-> Насколько нужен переезд? "Смерть от тысячи порезов" и тех долг. Чего не хватает? Чем может помочь методология?
-
-> См. доклад [Илья Климова про необходимость и порядок рефакторинга](https://youtu.be/aOiJ3k2UvO4)
-
-![approaches-themed-bordered](/img/approaches.png)
-
-## Какой план? {#whats-the-plan}
-
-### 1. Унификация кодовой базы {#1-unification-of-the-code-base}
-
-```diff
-- ├── products/
-- | ├── components/
-- | ├── containers/
-- | ├── store/
-- | ├── styles/
-- ├── checkout/
-- | ├── components/
-- | ├── containers/
-- | ├── helpers/
-- | ├── styles/
-+ └── src/
- ├── actions/
- ├── api/
-+ ├── components/
-+ ├── containers/
- ├── constants/
- ├── epics/
-+ ├── i18n/
- ├── modules/
-+ ├── helpers/
-+ ├── pages/
-- ├── routes/
-- ├── utils/
- ├── reducers/
-- ├── redux/
- ├── selectors/
-+ ├── store
-+ ├── styles/
- ├── App.jsx
- └── index.jsx
-```
-
-
-### 2. Собираем вместе излишне раздробленное {#2-putting-together-the-destructive-decoupled}
-
-```diff
- └── src/
-- ├── actions/
- ├── api/
-- ├── components/
-- ├── containers/
-- ├── constants/
-- ├── epics/
-+ ├── entities/{...}
-+ | ├── ui
-+ | ├── model/{actions, selectors, ...}
-+ | ├── lib
- ├── i18n/
- | # Временно можем положить сюда оставшиеся сегменты
-+ ├── modules/{helpers, constants}
-- ├── helpers/
- ├── pages/
-- ├── reducers/
-- ├── selectors/
-- ├── store/
- ├── styles/
- ├── App.jsx
- └── index.jsx
-```
-
-### 3. Выделяем скоупы ответственности {#3-allocate-scopes-of-responsibility}
-
-```diff
- └── src/
-- ├── api/
-+ ├── app/
-+ | ├── index.jsx
-+ | ├── style.css
- ├── pages/
-+ ├── features/
-+ | ├── add-to-cart/{ui, model, lib}
-+ | ├── choose-delivery/{ui, model, lib}
-+ ├── entities/{...}
-+ | ├── delivery/{ui, model, lib}
-+ | ├── cart/{ui, model, lib}
-+ | ├── product/{ui, model, lib}
-+ ├── shared/
-+ | ├── api/
-+ | ├── lib/ # helpers
-+ | | ├── i18n/
-+ | ├── config/ # constants
-- ├── i18n/
-- ├── modules/{helpers, constants}
- └── index.jsx
-```
-
-### 4. Final ?
-
-> Про оставшиеся проблемы и насколько стоит их устранять
-
-## См. также {#see-also}
-
-- [(Доклад) Илья Климов - Крысиные бега бесконечного рефакторинга: как не дать техническому долгу убить мотивацию и продукт](https://youtu.be/aOiJ3k2UvO4)
-- [(Доклад) Илья Азин - Архитектура Frontend проектов](https://youtu.be/SnzPAr_FJ7w)
- - В докладе в том числе рассмотрены подходы к архитектуре и стоимости рефакторинга
\ No newline at end of file
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md
index c0f4d33e02..5613ab5d27 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md
@@ -1,5 +1,5 @@
---
-sidebar_position: 4
+sidebar_position: 2
---
# Миграция с v1
@@ -157,10 +157,10 @@ sidebar_position: 4
- [Новые идеи v2 с пояснениями (atomicdesign-chat)][ext-tg-v2-draft]
- [Обсуждение абстракций и нейминга для новой версии методологии (v2)](https://github.com/feature-sliced/documentation/discussions/31)
-[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion
+[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion
[refs-adaptability]: /docs/about/understanding/naming
-[ext-v1]: https://featureslices.dev/v1.0.html
+[ext-v1]: https://feature-sliced.github.io/featureslices.dev/v1.0.html
[ext-tg-spb]: https://t.me/feature_slices
[ext-fdd]: https://github.com/feature-sliced/documentation/tree/rc/feature-driven
[ext-fdd-issues]: https://github.com/kof/feature-driven-architecture/issues
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md
new file mode 100644
index 0000000000..371843d981
--- /dev/null
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md
@@ -0,0 +1,45 @@
+---
+sidebar_position: 3
+---
+
+# Миграция с v2.0 на v2.1
+
+Основным изменением в v2.1 является новая ментальная модель разложения интерфейса — сначала страницы.
+
+В версии FSD 2.0 рекомендовалось найти сущности и фичи в вашем интерфейсе, рассматривая даже малейшие части представления сущностей и интерактивность как кандидаты на декомпозицию. Затем вы бы могли строить виджеты и страницы из сущностей и фич. В этой модели декомпозиции большая часть логики находилась в сущностях и фичах, а страницы были просто композиционными слоями, которые сами по себе не имели большого значения.
+
+В версии FSD 2.1 мы рекомендуем начинать со страниц, и возможно даже на них и остановиться. Большинство людей уже знают, как разделить приложение на страницы, и страницы также часто являются отправной точкой при попытке найти компонент в кодовой базе. В новой модели декомпозиции вы храните большую часть интерфейса и логики в каждой отдельной странице, а повторно используемый фундамент — в Shared. Если возникнет необходимость переиспользования бизнес-логики на нескольких страницах, вы можете переместить её на слой ниже.
+
+Другим нововведением в Feature-Sliced Design 2.1 является стандартизация кросс-импортов между сущностями с помощью `@x`-нотации.
+
+## Как мигрировать {#how-to-migrate}
+
+В версии 2.1 нет ломающих изменений, что означает, что проект, написанный с использованием FSD v2.0, также является валидным проектом в FSD v2.1. Однако мы считаем, что новая ментальная модель более полезна для команд и особенно для обучения новых разработчиков, поэтому рекомендуем внести небольшие изменения в вашу декомпозицию.
+
+### Соедините слайсы
+
+Простой способ начать — запустить на проекте наш линтер, [Steiger][steiger]. Steiger построен с новой ментальной моделью, и наиболее полезные правила будут:
+
+- [`insignificant-slice`][insignificant-slice] — если сущность или фича используется только на одной странице, это правило предложит целиком переместить код этой сущности или фичи прямо в эту страницу.
+- [`excessive-slicing`][excessive-slicing] — если у слоя слишком много слайсов, это обычно означает, что декомпозиция слишком мелкая. Это правило предложит объединить или сгруппировать некоторые слайсы, чтобы помочь в навигации по проекту.
+
+```bash
+npx steiger src
+```
+
+Это поможет вам определить, какие слайсы используются только один раз, чтобы вы могли ещё раз подумать, действительно ли они необходимы. Помните, что слой формирует своего рода глобальное пространство имен для всех слайсов внутри него. Точно так же, как вы не захотите загрязнять глобальное пространство имен переменными, которые используются только один раз, вы должны относиться к месту в пространстве имен слоя как к ценному месту, которое следует использовать сдержанно.
+
+### Стандартизируйте кросс-импорты
+
+Если у вас были кросс-импорты в вашем проекте до этого (мы не осуждаем!), вы теперь можете воспользоваться новой нотацией для кросс-импортов в Feature-Sliced Design — `@x`-нотацией. Она выглядит так:
+
+```ts title="entities/B/some/file.ts"
+import type { EntityA } from "entities/A/@x/B";
+```
+
+Чтоб узнать больше об этом, обратитесь к разделу [Публичный API для кросс-импортов][public-api-for-cross-imports] в разделе справочника.
+
+[insignificant-slice]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice
+[steiger]: https://github.com/feature-sliced/steiger
+[excessive-slicing]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/excessive-slicing
+[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx
index ea8e25032d..db40bcd655 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-react-query.mdx
@@ -35,7 +35,7 @@ sidebar_position: 10
```
Если среди сущностей есть связи (например, у сущности Страна есть поле-список сущностей Город), то можно воспользоваться
-[экспериментальным подходом к организованным кросс-импортам через @x-нотацию](https://github.com/feature-sliced/documentation/discussions/390#discussioncomment-5570073) или рассмотреть альтернативное решение ниже.
+[публичным API для кросс-импортов][public-api-for-cross-imports] или рассмотреть альтернативное решение ниже.
### Альтернативное решение — хранить запросы в общем доступе.
@@ -432,3 +432,5 @@ export const apiClient = new ApiClient(API_URL);
- [(GitHub) Пример проекта](https://github.com/ruslan4432013/fsd-react-query-example)
- [(CodeSandbox) Пример проекта](https://codesandbox.io/p/github/ruslan4432013/fsd-react-query-example/main)
- [О фабрике запросов](https://tkdodo.eu/blog/the-query-options-api)
+
+[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports
diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx
index 185133bedb..35e05047ec 100644
--- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx
+++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/tech/with-sveltekit.mdx
@@ -60,10 +60,10 @@ export default config;
fsd pages home
```
-Создайте файл `home-page.vue` внутри сегмента ui, откройте к нему доступ с помощью Public API
+Создайте файл `home-page.svelte` внутри сегмента ui, откройте к нему доступ с помощью Public API
```ts title="src/pages/home/index.ts"
-export { default as HomePage } from './ui/home-page';
+export { default as HomePage } from './ui/home-page.svelte';
```
Создайте роут для этой страницы внутри слоя `app`:
@@ -82,7 +82,7 @@ export { default as HomePage } from './ui/home-page';
│ │ │ ├── index.ts
```
-Добавьте внутрь `index.svelte` файла компонент вашей страницы:
+Добавьте внутрь `+page.svelte` файла компонент вашей страницы:
```html title="src/app/routes/+page.svelte"