尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

使用 Leptos + Axum 构建同构 Hacker News 应用:SSR 与 CSR 共存的完整实战示例解析

使用 Leptos + Axum 构建同构 Hacker News 应用:SSR 与 CSR 共存的完整实战示例解析 使用 Leptos Axum 构建同构 Hacker News 应用SSR 与 CSR 共存的完整实战示例解析【免费下载链接】leptosBuild fast web applications with Rust.项目地址: https://gitcode.com/GitHub_Trending/le/leptos本篇指南深入剖析 hackernews_axum 示例——一个基于 Leptos 框架、以 Axum 作为服务端的 Hacker News 克隆应用。它在一个仓库中同时展示了客户端渲染CSR与服务端渲染SSR 客户端水合hydration两种运行模式覆盖了 Axum 集成、路由懒加载、服务端/客户端同构数据获取等核心工程能力。读完本文你将掌握 Leptos 应用如何接入 Axum 服务器、如何通过 feature 区分构建目标以及如何组织一套可复制的同构数据请求模式。示例定位一个仓库两种渲染模式根据 hackernews_axum 的 README这个示例是 Hacker News 网站的基础克隆核心价值在于它展示了 Leptos 在同一个仓库中同时构建客户端渲染应用和带水合的服务端渲染应用的能力。它与仓库中另一个 hackernews 示例 的区别在于本示例使用Axum作为 HTTP 服务器。这一点在 main.rs 中得到了印证——服务端入口直接引入了axum::routing::get与Router并通过leptos_axum集成层完成 Leptos 应用与 Axum 路由的对接。项目结构与关键文件examples/hackernews_axum/ ├── Cargo.toml # feature 划分与 cargo-leptos 构建配置 ├── Makefile.toml # cargo-make 任务入口 ├── index.html # TrunkCSR 模式的 HTML 入口 ├── rust-toolchain.toml # 固定 wasm32-unknown-unknown 编译目标 ├── style.css # 示例样式经 Lightning CSS 优化 ├── public/favicon.ico # 站点图标 └── src/ ├── main.rs # 服务端入口SSR与客户端入口CSR双实现 ├── lib.rs # 共享的 App 组件、shell 与路由声明 ├── api.rs # 同构数据获取层SSR 用 reqwestCSR 用 gloo-net ├── error_template.rs # 错误模板 └── routes/ ├── nav.rs # 顶部导航栏组件 ├── stories.rs # 新闻列表页top / new / show / ask / job ├── story.rs # 新闻详情页含嵌套评论树 └── users.rs # 用户信息页从文件结构可以看出一个典型的分层思路main.rs只负责启动什么lib.rs定义应用是什么api.rs抽象数据从哪来routes/目录承载各页面组件的实现。快速启动两条开箱即用的命令原文档给出了两种快速启动方式分别对应 Leptos 生态中两套主流构建工具命令适用模式工具trunk serve --openCSR默认 featureTrunkcargo leptos watchSSR 水合cargo-leptostrunk serve --open以客户端渲染模式运行。默认 feature 为csr见 Cargo.tomlTrunk 会编译 WASM 并启动开发服务器适合纯前端快速预览。cargo leptos watch以服务端渲染模式运行。cargo-leptos 依据 Cargo.toml 中的[package.metadata.leptos]配置分别以ssrfeature 编译 bin 目标、以hydratefeature 编译 lib 目标并开启自动热重载。环境准备来自 Examples README在运行前需要准备以下工具链安装 Rust 与 Nightly 工具链rustup toolchain install nightly添加 WASM 编译目标rustup target add wasm32-unknown-unknown本示例的 rust-toolchain.toml 已固定该目标安装构建工具cargo install trunkCSR 模式或cargo install cargo-leptosSSR 模式可选安装cargo install cargo-make然后可执行cargo make start/cargo make stop管理示例进程见 Makefile.tomlFeature 矩阵csr / hydrate / ssr 三态切换Hackernews_axum 的 Cargo.toml 是理解整个示例的关键[features] default [csr] csr [leptos/csr] hydrate [leptos/hydrate] ssr [ dep:axum, dep:tower, dep:tower-http, dep:tokio, dep:http, leptos/ssr, leptos_axum, leptos_meta/ssr, leptos_router/ssr, ]csr纯客户端渲染不引入任何服务器依赖所有网络请求通过gloo-net在浏览器内发起hydrate为服务端输出的 HTML 提供水合入口leptos::mount::hydrate_body会把事件绑定到已存在的 DOM 上见 lib.rsssr开启 Axum、Tokio、tower 等服务器依赖同时联动开启leptos_meta、leptos_router的 SSR 能力。注意csr与ssr相互独立、可分别编译这正是同一仓库两种模式的工程基础。cargo-all-features的 denylist 配置Cargo.toml则避免了对 axum 等可选依赖的重复组合测试。cargo-leptos 关键配置项Cargo.toml 中[package.metadata.leptos]段的参数含义如下配置项示例值作用output-namehackernews_axumJS/WASM 产物命名默认取 crate 名site-roottarget/site构建输出根目录重建时会清空该目录全部内容site-pkg-dirpkg站点根目录下存放 JS/WASM/CSS 产物的子目录style-file./style.css源 CSS经 Lightning CSS 优化后写入site-root/site-pkg/app.cssassets-dirpublic该目录下的文件会被复制到站点根目录site-addr127.0.0.1:3000服务器监听地址与端口reload-port3001自动重载监控端口bin-features[ssr]编译 bin 目标使用的 featurebin-default-featuresfalse编译 bin 目标时不使用默认 featurelib-features[hydrate]编译 lib 目标使用的 featurelib-default-featuresfalse编译 lib 目标时不使用默认 feature这套配置实现了cargo leptos watch时服务端用ssr、客户端用hydrate的分目标构建这也是 SSR 水合应用的标准构建方式。Axum 服务端集成从路由生成到文件服务main.rs 完整展示了 Leptos 应用挂载到 Axum 的过程#[cfg(feature ssr)] #[tokio::main] async fn main() { use axum::{routing::get, Router}; use hackernews_axum::{shell, App}; use leptos::config::get_configuration; use leptos_axum::{generate_route_list, LeptosRoutes}; let conf get_configuration(Some(Cargo.toml)).unwrap(); let leptos_options conf.leptos_options; let addr leptos_options.site_addr; let routes generate_route_list(App); let app Router::new() .route(/favicon.ico, get(|| async { ... })) .leptos_routes(leptos_options, routes, { move || shell(leptos_options.clone()) }) .fallback(leptos_axum::file_and_error_handler(shell)) .with_state(leptos_options); let listener tokio::net::TcpListener::bind(addr).await.unwrap(); axum::serve(listener, app.into_make_service()).await.unwrap(); }核心调用链可以拆解为四个环节读取配置get_configuration(Some(Cargo.toml))从 Cargo.toml 的[package.metadata.leptos]段加载LeptosOptions其中的site_addr127.0.0.1:3000直接决定监听地址生成路由表generate_route_list(App)静态遍历App组件中声明的Route树生成 Leptos 可识别的路径列表注册动态路由.leptos_routes(...)为这些路径统一注册服务端渲染入口——请求到达时由 Leptos 执行 SSR 输出完整 HTML并内联水合脚本文件与错误兜底.fallback(leptos_axum::file_and_error_handler(shell))负责提供target/site下的静态资源JS/WASM/CSS以及错误页回退。对应的 HTML 外壳由shell函数定义lib.rs其中AutoReload在开发期注入热重载脚本HydrationScripts输出水合所需脚本MetaTags/为leptos_meta提供元信息出口pub fn shell(options: LeptosOptions) - impl IntoView { view! { !DOCTYPE html html langen head meta charsetutf-8/ meta nameviewport contentwidthdevice-width, initial-scale1/ AutoReload optionsoptions.clone() / HydrationScripts options/ MetaTags/ /head body App/ /body /html } }此外还有一条TrunkCSR入口当未启用ssrfeature 时main.rs编译为纯客户端入口直接mount_to_body(App)与 SSR 入口通过#[cfg(feature ssr)]互斥共存main.rs。同构数据获取一套代码两种网络栈Hacker News 类应用的核心是拉取外部 API。示例没有把数据获取逻辑写在组件里而是收敛在 api.rs 中并通过#[cfg]实现同一函数签名、两种实现#[cfg(not(feature ssr))] // 客户端gloo-net AbortController pub fn fetch_apiT(path: str) - impl FutureOutput OptionT Send _ { SendWrapper::new(async move { let abort_controller SendWrapper::new(web_sys::AbortController::new().ok()); let abort_signal abort_controller.as_ref().map(|a| a.signal()); on_cleanup(move || { if let Some(abort_controller) abort_controller.take() { abort_controller.abort() // 离开页面时中止未完成的请求 } }); gloo_net::http::Request::get(path) .abort_signal(abort_signal.as_ref()) .send().await.ok()?.json().await.ok() }) } #[cfg(feature ssr)] // 服务端reqwest pub async fn fetch_apiT(path: str) - OptionT { reqwest::get(path).await.ok()?.json().await.ok() }SSR 端在服务器进程内用reqwest直接请求上游 API将数据渲染进 HTML 后返回CSR 端在浏览器内用gloo-net请求并利用web_sys::AbortController与on_cleanup钩子实现离开页面即中止在途请求的体验优化契约一致两者都返回OptionT调用方各页面组件无需区分当前运行环境这保证了同构代码的书写体验。数据模型同样集中在 api.rsStory、Comment、User三个结构体用serde反序列化上游 JSON。值得注意的细节是Story中#[serde(alias type)] pub story_type: Stringapi.rs——由于type是 Rust 关键字这里通过 alias 映射外部字段并利用#[serde(default)]兜底可选字段。路由与页面组件懒加载、资源与过渡动画路由声明与懒加载lib.rs 使用leptos_router声明了三条路由Router set_is_routing div classrouting-progress RoutingProgress is_routing max_timeDuration::from_millis(250)/ /div Nav / main FlatRoutes fallback|| Not found. Route path(StaticSegment(users), ParamSegment(id)) view{Lazy::UserRoute::new()}/ Route path(StaticSegment(stories), ParamSegment(id)) view{Lazy::StoryRoute::new()}/ Route pathOptionalParamSegment(stories) viewStories/ /FlatRoutes /main /RouterStaticSegment/ParamSegment/OptionalParamSegment组合出/users/:id、/stories/:id、/stories?pageN等路径Lazy::UserRoute::new()与Lazy::StoryRoute::new()把用户页和故事详情页拆分为懒加载块只有访问对应路由时才加载其 WASM/JS 代码RoutingProgress配合set_is_routing信号在异步数据加载超过 250ms 时显示路由进度条避免用户感知到卡顿。列表页Resource Transition 的数据流stories.rs 是列表页实现展示了 Leptos 响应式数据流的标准写法let stories Resource::new( move || (page(), story_type()), // 依赖页码与分类变化时自动重取 move |(page, story_type)| async move { let path format!({}?page{}, category(story_type), page); api::fetch_api::Vecapi::Story(api::story(path)).await }, );Resource::new的第一个闭包声明响应式依赖第二个闭包执行异步获取Transition fallback... set_pending在切换页面时保留旧内容并显示加载态数据到达后平滑更新而不是整页闪烁Show用于在None请求失败时显示错误提示For以story.id为 key 高效复用列表项 DOM分页导航通过读取 URL 的 query 参数use_query_map与路径参数use_params_map实现 prev/more 链接直接生成新的 URL天然支持前进后退与刷新恢复。详情页lazy_route 与可折叠评论树story.rs 展示了#[lazy_route]宏的用法——把数据预取与视图渲染拆成data()与view()两个方法实现进入路由前先并行加载数据的体验#[lazy_route] impl LazyRoute for StoryRoute { fn data() - Self { let params use_params_map(); let story Resource::new_blocking( move || params.read().get(id).unwrap_or_default(), move |id| async move { api::fetch_api::api::Story(api::story(format!(item/{id}))).await }, ); Self { story } } fn view(this: Self) - AnyView { /* Suspense 详情渲染 */ } }评论部分采用递归组件Comment每层评论通过signal(true)记录展开状态点击[-]/[] N replies切换折叠story.rsinner_htmlcomment.content直接渲染服务端返回的 HTML 内容。pluralize函数负责英文单复数的正确输出细节上保证了与 Hacker News 原文案的一致性。用户页与导航users.rs 同样基于#[lazy_route]展示注册时间created、Karma 值、个人简介inner_html渲染about并外链到 Hacker News 的 submissions / threads 页面。nav.rs 提供Home / New / Show / Ask / Jobs五个导航链接全部使用leptos_router::components::A组件实现客户端内跳转不刷新页面。从示例到生产可迁移的工程要点综合源码可以提炼出四条可直接迁移到自研项目的模式双入口 feature 矩阵用csr / hydrate / ssr三个 feature 把客户端入口、水合入口、服务端入口隔离在同一 crate 中#[cfg(feature ...)]控制编译内容[package.metadata.leptos]配置分目标构建参考 main.rs 与 Cargo.tomlAxum 四步接入get_configuration读配置 →generate_route_list生成路由表 →.leptos_routes注册 SSR →.fallback(file_and_error_handler)兜底静态资源与错误页同构数据层把网络请求收敛到一个模块用 cfg 区分reqwest服务端与gloo-net AbortController客户端调用方只依赖OptionT返回契约api.rs性能分层Lazy路由按需加载代码块、Resource::new_blocking提前预取数据、Transition保持界面连贯、RoutingProgress在慢网络下给出视觉反馈四者组合即可获得接近原版 Hacker News 的浏览手感。结语hackernews_axum 示例在几十行配置与不到千行的业务代码中完整覆盖了 Leptos 全栈开发的主干路径Axum 服务端渲染、客户端水合、懒加载路由、响应式资源与同构数据获取。它以Hacker News 克隆这一足够真实的需求为载体让每一种模式都能在 src 目录下找到对应的可运行实现。无论你是想为项目引入 SSR、还是评估 Leptos Axum 的组合这个示例都是最直接的起点。【免费下载链接】leptosBuild fast web applications with Rust.项目地址: https://gitcode.com/GitHub_Trending/le/leptos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表