diff --git a/examples/ssr-demo/src/pages/index.tsx b/examples/ssr-demo/src/pages/index.tsx index e5b750b5354c..279b00b9d5c1 100644 --- a/examples/ssr-demo/src/pages/index.tsx +++ b/examples/ssr-demo/src/pages/index.tsx @@ -1,6 +1,7 @@ import { - IServerLoaderArgs, Link, + MetadataLoader, + ServerLoader, useClientLoaderData, useServerInsertedHTML, useServerLoaderData, @@ -51,8 +52,26 @@ export async function clientLoader() { return { message: 'data from client loader of index.tsx' }; } -export async function serverLoader({ request }: IServerLoaderArgs) { - const { url } = request; +export const serverLoader: ServerLoader = async (req) => { + const url = req!.request.url; await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000)); return { message: `data from server loader of index.tsx, url: ${url}` }; -} +}; + +// SEO-设置页面的TDK +export const metadataLoader: MetadataLoader<{ message: string }> = ( + serverLoaderData, +) => { + return { + title: '开发者学堂 - 支付宝开放平台', + description: '支付宝小程序开发入门实战经验在线课程,让更多的开发者获得成长', + keywords: ['小程序开发', '入门', '实战', '小程序云'], + lang: 'zh-CN', + metas: [ + { + name: 'msg', + content: serverLoaderData.message, + }, + ], + }; +}; diff --git a/packages/preset-umi/src/features/ssr/ssr.ts b/packages/preset-umi/src/features/ssr/ssr.ts index ec65e3c5971e..d5fa28b509c0 100644 --- a/packages/preset-umi/src/features/ssr/ssr.ts +++ b/packages/preset-umi/src/features/ssr/ssr.ts @@ -105,7 +105,16 @@ export function useServerInsertedHTML(callback: () => React.ReactNode): void { api.writeTmpFile({ path: 'types.d.ts', content: ` -export type { IServerLoaderArgs, UmiRequest } from '${winPath(ssrTypesPath)}' +export type { + // server loader + IServerLoaderArgs, + UmiRequest, + ServerLoader, + // metadata loader + MetadataLoader, + IMetadata, + IMetaTag, +} from '${winPath(ssrTypesPath)}' `, }); }); diff --git a/packages/preset-umi/src/features/tmpFiles/routes.ts b/packages/preset-umi/src/features/tmpFiles/routes.ts index 270520f1da51..8752d3ddb21f 100644 --- a/packages/preset-umi/src/features/tmpFiles/routes.ts +++ b/packages/preset-umi/src/features/tmpFiles/routes.ts @@ -165,6 +165,7 @@ export async function getRoutes(opts: { : []; if (enableSSR) { routes[id].hasServerLoader = exports.includes('serverLoader'); + routes[id].hasMetadataLoader = exports.includes('metadataLoader'); } if (enableClientLoader && exports.includes('clientLoader')) { routes[id].clientLoader = `clientLoaders['${id}']`; diff --git a/packages/renderer-react/src/server.tsx b/packages/renderer-react/src/server.tsx index 9414745d7f08..bf0695372323 100644 --- a/packages/renderer-react/src/server.tsx +++ b/packages/renderer-react/src/server.tsx @@ -1,3 +1,4 @@ +import type { IMetadata } from '@umijs/server/dist/types'; import React from 'react'; import { StaticRouter } from 'react-router-dom/server'; import { AppContext } from './appContext'; @@ -5,16 +6,18 @@ import { Routes } from './browser'; import { createClientRoutes } from './routes'; import { IRouteComponents, IRoutesById } from './types'; -// Get the root React component for ReactDOMServer.renderToString -export async function getClientRootComponent(opts: { +interface IHtmlProps { routes: IRoutesById; routeComponents: IRouteComponents; pluginManager: any; location: string; loaderData: { [routeKey: string]: any }; manifest: any; - withoutHTML?: boolean; -}) { + metadata?: IMetadata; +} + +// Get the root React component for ReactDOMServer.renderToString +export async function getClientRootComponent(opts: IHtmlProps) { const basename = '/'; const components = { ...opts.routeComponents }; const clientRoutes = createClientRoutes({ @@ -57,36 +60,33 @@ export async function getClientRootComponent(opts: { {rootContainer} ); - if (opts.withoutHTML) { - return ( - <> -
{app}
-