--- url: /guides/advanced-features/bff.md --- # BFF BFF (Backends for Frontends) is an architectural pattern primarily used to address issues of data aggregation in front-end and back-end collaboration. Under the BFF architecture, front-end applications do not communicate directly with backend services. Instead, they interact with backend services through a dedicated BFF middleware layer, custom-made for the front end. The main problems it tries to solve include: - Aggregation, mapping, clipping, and proxying of lower-level APIs according to their own business needs. - Cache data for some specific scenarios to improve performance and thus improve user experience. - Quickly development of new products based on existing interfaces. - Interface with third-party systems, such as login authentication. Modern.js officially supported the BFF and provided the **Integrated BFF** to further strengthen the BFF's capabilities, mainly including the following capabilities: - Quick development and debugging go live, running, building, and deploying BFF code in the same project. - Minimal pure function call, directly import BFF function on the front end, and can be automatically converted into HTTP request when called. - No private protocol, follow RESTful API specification, all BFF interfaces are standardized. - Full TypeScript support. - Meet user preferences and support multi-frame extension writing. --- url: /guides/advanced-features/bff/cross-project.md --- # Cross-Project Invocation Based on the BFF architecture, Modern.js provides cross-project invocation capabilities, allowing BFF functions created in one project to be invoked by other projects through integrated calls, enabling function sharing and feature reuse across projects. Cross-project invocation consists of **producer** and **consumer** sides. The producer is responsible for creating and providing BFF services while generating integrated invocation SDK, and the consumer initiates requests through these SDK. ## BFF Producer Enable cross-project invocation via configuration. Projects with BFF capabilities enabled can act as BFF producers, or you can create standalone BFF applications. When executing `dev` or `build`, the following artifacts for consumers will be automatically generated: - API functions under the `dist/client` directory - Runtime configuration functions under the `dist/runtime` directory - Interface exports defined in `exports` field of `package.json` - File list for npm publication specified in `files` field of `package.json` ### Existing BFF-enabled Projects 1. Enable Cross-Project Invocation Ensure the current project has BFF enabled with API files defined under `api/lambda`. Add the following configuration: ```ts title="modern.config.ts" export default defineConfig({ bff: { crossProject: true, } }); ``` 2. Generate SDK Type Files To provide type hints for the integrated invocation SDK, enable the `declaration` option in TypeScript configuration: ```ts title="tsconfig.json" "compilerOptions": { "declaration": true, } ``` ## BFF Consumer :::info You can initiate requests to BFF producers from projects using any framework via the SDK. ::: ### Intra-Monorepo Invocation When producer and consumer are in the same Monorepo, directly import the SDK. API functions reside under `${package_name}/api`: ```ts title="src/routes/page.tsx" import { useState, useEffect } from 'react'; import { get as hello } from '${package_name}/api/hello'; export default () => { const [text, setText] = useState(''); useEffect(() => { hello().then(setText); }, []); return
{text}
; }; ``` Now run the `dev` command to start the project, and access `http://localhost:8080/` to find that the request for `/api/hello` has been intercepted:  Finally, modify the frontend code `src/routes/page.tsx`, and call the login interface before accessing `/api/hello`: :::note This part does not implement a real login interface; the code is just for demonstration. ::: ```ts import { useState, useEffect } from 'react'; import { get as hello } from '@api/hello'; import { post as login } from '@api/login'; export default () => { const [text, setText] = useState(''); useEffect(() => { async function fetchAfterLogin() { const { code } = await login(); if (code === 0) { const { message } = await hello(); setText(message); } } fetchAfterLogin(); }, []); return{text}
; }; ``` Refresh the page, and you can see that the access to `/api/hello` is successful:  The above code simulates defining middleware in `server/Modern.server.ts` and implements a simple login function. Similarly, other functionalities can be implemented in this configuration file to extend the BFF Server. --- url: /guides/advanced-features/bff/frameworks.md --- # Runtime Framework Modern.js uses [Hono.js](https://hono.dev/) as the BFF and Server runtime framework, so you can [extend BFF Server](/guides/advanced-features/bff/extend-server.md) based on the Hono.js ecosystem. ### Getting Request Context Sometimes in BFF functions, it's necessary to obtain the request context to handle more logic. In such cases, you can use `useHonoContext` to get it: ```ts title="api/lambda/hello.ts" import { useHonoContext } from '@modern-js/server-runtime'; export const get = async () => { const c = useHonoContext(); console.info(`access url: ${c.req.url}`); return 'Hello Modern.js'; }; ``` :::info For more details, refer to [useHonoContext](/apis/app/runtime/bff/use-hono-context.md). ::: ### Getting Cookies When getting cookies in BFF functions, you need to get the request context through `useHonoContext`, then use `c.req.header('cookie')` to get the Cookie string and parse it manually: ```ts title="api/lambda/cookies.ts" import { Api, Get } from '@modern-js/plugin-bff/server'; import { useHonoContext } from '@modern-js/server-runtime'; // Helper function to parse Cookie string function parseCookies( cookieHeader: string | undefined, ): RecordCurrent language: {language}
{t('description', { name: 'Modern.js' })}
{t('welcome', { name: 'John' })}
{t('invite', { name: 'Alice', project: 'Modern.js' })}
> ); ``` ### Nested Interpolation You can directly pass objects or multi-level variables: ```json { "greeting": "Hello, {{user.name}}, you have {{user.notifications}} new messages" } ``` ```tsx t('greeting', { user: { name: 'Jay', notifications: 3 }, }); ``` ### Formatted Interpolation Format numbers, dates, etc. through the `interpolation.format` function: ```ts export default defineRuntimeConfig({ i18n: { initOptions: { interpolation: { format(value, format, lng) { if (format === 'currency') { return new Intl.NumberFormat(lng, { style: 'currency', currency: lng === 'zh' ? 'CNY' : 'USD', }).format(Number(value)); } if (value instanceof Date) { return new Intl.DateTimeFormat(lng, { dateStyle: 'medium' }).format( value, ); } return value; }, }, }, }, }); ``` ```tsx t('formattedValue', { value: 99.5, format: 'currency' }); ``` ### Escaping Interpolation `react-i18next` escapes interpolation values by default to prevent XSS. If you need to render safe HTML, you need to explicitly enable `interpolation.escapeValue = false` and ensure the data is trustworthy. ## Pluralization Pluralization automatically selects the appropriate word form based on the language, depending on the `count` parameter. ```json { "item": "1 item", "item_plural": "{{count}} items", "item_0": "no items" } ``` ```tsx t('item', { count: 0 }); // no items t('item', { count: 1 }); // 1 item t('item', { count: 5 }); // 5 items ``` Different languages have different pluralization rules, for example: - **English**: Singular, plural - **Russian**: Multiple forms such as one, few, many - **Chinese**: Usually only a single form, can use `_0` key to override special text 💡 If you need to customize pluralization rules, you can extend through `i18next.services.pluralResolver`. See advanced usage for details. ## Nested Translation Structure Nested structures can intuitively reflect UI hierarchy. ```json { "common": { "button": { "submit": "Submit", "cancel": "Cancel" } } } ``` Use dots to access in code: ```tsx const { t } = useTranslation(); t('common.button.submit'); ``` Advantages of nested structures: - Avoid lengthy key names - Easy to view module text as a whole in JSON - Can be combined with `keyPrefix` to simplify calls: `useTranslation('common', { keyPrefix: 'button' })` ## Fallback Language When the current language is missing a key, it will continue searching according to the fallback language chain. ```ts export default defineRuntimeConfig({ i18n: { initOptions: { lng: 'zh-CN', fallbackLng: { 'zh-CN': ['zh', 'en'], default: ['en'], }, }, }, }); ``` :::tip You can fallback from regional languages (such as `zh-CN`) to general languages (`zh`), and finally to the default language (`en`), ensuring that all keys have available text. ::: ## Language Detection i18next automatically identifies user language through language detection plugins. Modern.js plugin has built-in browser and server support. ```ts import LanguageDetector from 'i18next-browser-languagedetector'; import i18next from 'i18next'; i18next.use(LanguageDetector).init({ supportedLngs: ['zh', 'en', 'ja'], detection: { order: ['path', 'cookie', 'localStorage', 'navigator'], lookupCookie: 'i18next', lookupLocalStorage: 'i18nextLng', }, }); ``` In Modern.js, you can directly enable built-in detection in the plugin configuration: ```ts i18nPlugin({ localeDetection: { i18nextDetector: true, languages: ['zh', 'en'], detection: { order: ['path', 'cookie', 'header'], }, }, }); ``` :::warning After enabling detection, there is no need to explicitly set `lng` in `init`. If you manually call `changeLanguage()` without passing a language, it will also automatically infer based on the detection configuration. ::: --- url: /guides/advanced-features/international/best-practices.md --- # Best Practices ## Resource File Organization It's recommended to organize resource files as follows: ``` locales/ ├── en/ │ ├── translation.json # Default namespace │ ├── common.json # Common translations │ └── errors.json # Error messages └── zh/ ├── translation.json ├── common.json └── errors.json ``` **Configure Multiple Namespaces**: ```ts export default defineRuntimeConfig({ i18n: { initOptions: { ns: ['translation', 'common', 'errors'], defaultNS: 'translation', }, }, }); ``` **Using Namespaces**: ```tsx import { useTranslation } from 'react-i18next'; function MyComponent() { const { t } = useTranslation('common'); returnCurrent language: {language}
Click me!
This part is static.
{/* Client Component can be seamlessly embedded in Server Component */}Something went wrong! {error.message}
; } ``` ## Controlling When to Wait for Full HTML Streaming improves perceived speed, but in some cases (SEO crawlers, A/B buckets, compliance pages) you may want to wait for all content before sending the response. Modern.js decides the streaming mode with this priority: 1. Request header `x-should-stream-all` (set per-request in middleware). 2. Env `MODERN_JS_STREAM_TO_STRING` (forces full HTML). 3. [isbot](https://www.npmjs.com/package/isbot) check on `user-agent` (bots get full HTML). 4. Default: stream shell first. Set the header in your middleware to choose the behavior dynamically: ```ts title="middleware example" export const middleware = async (ctx, next) => { const ua = ctx.req.header('user-agent') || ''; const shouldWaitAll = /Lighthouse|Googlebot/i.test(ua) || ctx.req.path === '/marketing'; // Write a boolean string: true -> onAllReady, false -> onShellReady ctx.req.headers.set('x-should-stream-all', String(shouldWaitAll)); await next(); }; ``` ## Related Documentation - [Rendering Mode Overview](/guides/basic-features/render/overview.md) - [Server-Side Rendering (SSR)](/guides/basic-features/render/ssr.md) - [Rendering Cache](/guides/basic-features/render/ssr-cache.md) - [React Server Components (RSC)](/guides/basic-features/render/rsc.md) - Use with Streaming SSR - [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37) - React 18 Architecture Overview --- url: /guides/basic-features/routes/config-routes.md --- # Config Routes By default, Modern.js recommends using [Convention Routes](/guides/basic-features/routes/routes.md) as the way to define routes. At the same time, Modern.js also provides a config-based routing capability that can be used together with convention routes or used separately. ## When to Use Config Routes Config routes are particularly useful in the following scenarios: - **Need flexible route control**: When the file structure cannot directly map to the desired URL structure - **Multiple routes pointing to the same component**: Need to point different URL paths to the same page component - **Conditional routes**: Dynamically configure routes based on different conditions - **Legacy project migration**: Maintain the original routing structure when migrating from other routing systems - **Complex route naming**: Need to customize route paths without being limited by file directory structure ## Basic Usage In the `src` directory or each entry directory, you can define a `modern.routes.ts` file to configure routes: ```ts import { defineRoutes } from '@modern-js/runtime/config-routes' export default defineRoutes(({ route, layout, page, $ }, fileRoutes) => { return [ route("home.tsx", "/"), ] }) ``` ### Function Signature Description `defineRoutes` accepts a callback function with two parameters: 1. `routeFunctions`: An object containing `route`, `layout`, `page`, `$` functions 2. `fileRoutes`: An array of route configurations generated by convention routes Basic signature of route functions: - First parameter: File path relative to `modern.routes.ts` - Second parameter: Route path (optional, must be a string) - Third parameter: Array of child routes (optional) ## Route Functions Modern.js provides four main route configuration functions: :::tip Path Description The first parameter (path) of all route functions is a **relative path**, which will be concatenated with the parent path to generate the final route path: - **Root path**: `"/"` or `""` represents the root path of the current level - **Relative path**: Child route paths will be concatenated relative to the parent path - **Dynamic path**: Use `:param` syntax to represent dynamic parameters - **Wildcard path**: Use `"*"` syntax to match all paths ::: :::info - The first parameter (component file path) of functions like `route`, `layout`, `page`, `$` must point to real files in the current project. Files from `node_modules` and other repositories in Monorepo are not currently supported. - Path aliases are not supported (such as `@/pages/...`, `~/pages/...`, etc.); please use relative paths relative to `modern.routes.ts`. ::: ### `route` Function The `route` function is a general-purpose route configuration function that automatically determines whether to generate a page route or layout route based on whether there are child routes. It can replace `layout`, `page`, `$` and other functions. ```ts export default defineRoutes(({ route }, fileRoutes) => { return [ // When no child routes, automatically generates page route route("home.tsx", "/"), route("about.tsx", "about"), // When has child routes, automatically generates layout route // dashboard/layout.tsx needs to contain{info?.error.message}
{t('about')}
{t('about')}
; }; export default () => { return ({info?.error?.message}
Hello Modern.js!