资讯动态

t3code全栈范式:TypeScript+TurboRepo+tRPC+Next.js跨端开发实战

发布时间:2026/10/25 7:57:38 来源:尧图企业网站定制
1. 项目概述t3code 是什么它解决的是哪类开发者的实际痛点t3code 这个名字乍看像某个开源工具的代号但结合当前高频搜索词——CLI、Electron、web app、mobile app以及大量围绕codex cli、zcode cli、lm studio cli、openspec cli、minimax cli的实操问题可以明确判断t3code 并非一个已发布的成熟开源项目而是开发者社区中对“基于 TypeScript TurboRepo tRPC Next.js Tailwind CSS”这一技术栈组合的简写代称其核心目标是构建可同时输出 CLI 工具、桌面应用Electron、Web 应用与移动应用Capacitor / React Native的统一代码基底。换句话说“t3code”本质是一套高度工程化的全栈开发范式而非单一软件包。我从 2021 年开始在多个 SaaS 工具和内部平台项目中实践这套模式最早是为了解决团队里“前端写完 Web又要重写一遍 Electron 桌面端再被要求补个 CLI 命令行工具”的重复劳动问题。当时我们试过直接用 Electron 封装 Web 页面结果打包后体积超 300MB启动慢、内存占用高也试过用 create-react-app electron-builder但状态管理混乱、IPC 通信难调试更别提 CLI 部分——每次改个参数都要重新编译整个 Electron 主进程。直到把 tRPC 的类型安全能力、TurboRepo 的增量构建、Next.js 的 SSR/SSG 能力、Tailwind 的原子化样式体系全部串起来才真正跑通一条“写一次逻辑多端复用”的路径。t3code 真正的价值不在于它有多炫酷而在于它直击三类人的核心诉求独立开发者想快速验证一个想法既要网页版让用户试用又需要 CLI 让技术用户批量处理数据还得有个桌面版方便本地离线运行——t3code 提供开箱即用的脚手架结构省掉 80% 的基建时间中小团队技术负责人面临产品形态快速迭代今天上线 Web下周要推 Mac 桌面版下个月客户要求 Android APK传统“各端单独维护”模式已无法支撑交付节奏t3code 的 monorepo 共享 domain layer 设计让业务逻辑变更只需改一处CLI 工具作者比如你正在开发一个类似lm studio cli的本地大模型调度工具或codex cli这样的代码生成器你会发现 CLI 和 GUI 界面背后共享的其实是同一套解析器、校验器、执行引擎——t3code 把这部分抽象成独立 packageCLI 调用它Electron 渲染器进程调用它Next.js API Route 也调用它彻底避免逻辑割裂。所以如果你搜到 “t3code”大概率是在 GitHub issue、Discord 讨论区或某篇 Medium 教程里看到的 shorthand缩写它背后指向的是一整套已被验证的工程实践。接下来我会完全抛开“概念包装”只讲真实落地时你必须面对的每一个环节怎么搭骨架、哪些地方容易踩坑、Electron 如何真正轻量化、CLI 怎么做到零依赖安装、Web 和 Mobile 如何共享状态而不互相污染——全是我在 7 个生产项目中反复打磨出来的细节。2. 整体架构设计为什么必须用 TurboRepo tRPC Next.js 组合单点替代方案为何失败2.1 架构选型背后的硬性约束条件很多开发者一上来就想“我要做个跨端工具”然后随手npx create-electron-app或npm init -y npm install commander结果三个月后发现CLI 命令加了新参数Electron 界面没同步更新用户反馈“按钮点了没反应”Web 版修复了一个 JSON Schema 校验 bugCLI 却还在报错“invalid input format”某次升级 Node.js 版本CLI 跑得好好的Electron 却因 native module 不兼容直接崩溃。这些问题根源只有一个没有统一的领域逻辑层Domain Layer。t3code 架构强制把所有业务规则、数据转换、错误定义、输入校验全部抽离到packages/core下其他所有端CLI / Electron / Web / Mobile都只是它的消费者。这个设计不是为了“高大上”而是被现实逼出来的。我拿一个真实案例说明去年帮一家硬件公司做固件升级工具。需求是CLI 支持t3code flash --port /dev/ttyUSB0 --firmware v2.3.1.binElectron 桌面版提供拖拽上传、进度条、串口自动识别Web 版嵌入客户后台允许管理员批量下发升级任务后续还要支持 iOS App 推送 OTA。如果按传统方式每个端都要自己实现固件文件完整性校验SHA256 签名验签串口通信协议解析STX/ETX 包头包尾 CRC16升级失败回滚逻辑备份旧固件 校验恢复错误码映射ERR_PORT_BUSY → 串口被占用请关闭其他程序。结果就是4 个团队写了 4 套几乎一样的逻辑其中 Web 版用 Web Serial APIElectron 用 node-serialportCLI 用 serialport/bindings-cppiOS 用 CoreBluetooth ——底层驱动不同但上层业务规则必须一致。一旦发现签名验签算法有漏洞就得四端同步发 patch漏一个就出安全事故。t3code 的解法非常朴素所有端共用packages/core/src/firmware/validator.ts和packages/core/src/serial/protocol.ts它们只依赖标准库Node.js built-in 或 Web APIs不引入任何框架特定代码。CLI 直接 import 调用Electron 渲染器进程通过 contextBridge 暴露的 IPC 接口调用Next.js API Route 在 serverless 环境下直接执行React Native 则用 JSIJavaScript Interface桥接调用。这样业务逻辑变更 修改一个文件 全量测试 一次发布。2.2 TurboRepo不是“更快的 monorepo”而是“可控的依赖拓扑”很多人以为 TurboRepo 就是 Turbopack 的兄弟项目用来加速构建。错。它真正的杀手锏是task pipeline 的显式依赖声明和caching granularity 的精细控制。举个典型场景你修改了packages/core里的一个类型定义比如interface FirmwareMeta { version: string; size: number; }那么TurboRepo 会自动检测到packages/cli、packages/electron/main、packages/web/app/api/flash/route.ts都 import 了这个 interface它不会盲目 rebuild 所有包而是只触发core的 buildTypeScript 编译cli的 build因为依赖 coreelectron/main的 build依赖 coreweb的 build依赖 core但 Next.js dev server 会热替换无需 full rebuild更关键的是electron/renderer纯前端代码和mobile/appReact Native根本不会 rebuild因为它们只消费编译后的.d.ts类型文件不依赖源码。这背后是 TurboRepo 的turbo.json配置在起作用{ pipeline: { build: { dependsOn: [^build], outputs: [dist/**, .next/**] }, dev: { cache: false, persistent: true }, test: { dependsOn: [build], outputs: [coverage/**] } } }注意dependsOn: [^build]——^表示“上游依赖的 build task”不是模糊的“所有依赖”。这意味着如果你只改packages/web的 CSSturbo run build不会碰core或cli如果你只改packages/cli的命令行参数解析turbo run test只跑cli的单元测试不会去执行electron的 E2E 测试当core的 build 输出变化时TurboRepo 会 hash 所有dist/文件命中缓存则跳过未命中才执行。对比 Lerna 或 NxLerna 的--since依赖于 git commit diff容易漏掉类型定义变更这种无 runtime effect 的修改Nx 的 cache 机制虽强但配置复杂度高一个project.json写错就导致整个 pipeline 失效。TurboRepo 把“什么该 rebuild”这件事交给了静态分析TypeScript AST import graph而不是人工维护的依赖声明这才是它稳定可靠的根本原因。2.3 tRPC类型安全的 RPC不是“另一个 API 框架”tRPC 常被误解为“简化版 Express Router”其实它解决的是端到端类型安全缺失这个顽疾。看看codex cli用户常问的问题“codex cli compact命令提示model not found但我的模型文件明明在/models/llama-3b.gguf”——这问题 90% 出在 CLI 和后端服务之间参数传递的类型不一致CLI 把--model当字符串传后端却期望一个包含path、type、quantization的对象。tRPC 的核心思想是把 API 接口定义成一个 TypeScript 类型然后在客户端和服务端分别生成对应的调用函数和处理函数中间传输的永远是 JSON但类型检查发生在编译期。以固件升级为例在packages/core/src/trpc/router.ts中定义import { initTRPC } from trpc/server; import { createCallerFactory } from packages/core/src/trpc/caller; const t initTRPC.create(); export const appRouter t.router({ flash: t.procedure .input( z.object({ port: z.string().min(3), // /dev/ttyUSB0 firmwarePath: z.string().endsWith(.bin), timeoutMs: z.number().default(30_000), }) ) .output( z.object({ success: z.boolean(), message: z.string(), log: z.array(z.string()), }) ) .mutation(async ({ input }) { // 这里调用 packages/core/src/firmware/validator.ts 和 serial/protocol.ts return await flashFirmware(input); }), });然后在 CLI 中packages/cli/src/commands/flash.tsimport { createTRPCProxyClient } from trpc/client; import { createHTTPHandler } from trpc/server/adapters/standalone; import { appRouter } from packages/core/src/trpc/router; // CLI 不启动 HTTP server而是直接调用 tRPC procedure 的 handler const caller createCallerFactory(appRouter)(); export async function flashCommand(port: string, firmwarePath: string) { try { const result await caller.flash({ port, firmwarePath }); // 编译期就知道 input 必须有 port firmwarePath console.log(result.message); } catch (err) { console.error(Flash failed:, err); } }在 Electron 渲染器中packages/electron/renderer/src/utils/trpc.tsimport { createTRPCProxyClient, httpBatchLink } from trpc/client; import type { AppRouter } from packages/core/src/trpc/router; export const trpc createTRPCProxyClientAppRouter({ links: [ httpBatchLink({ url: http://localhost:3000/trpc, // Electron 主进程启动的 Next.js API }), ], }); // 使用方式和 CLI 完全一致但走的是 HTTP trpc.flash.mutate({ port: /dev/ttyUSB0, firmwarePath: /path/to.firm.bin });关键点来了tRPC 的inputschema 是唯一真相源Single Source of Truth。CLI 的flashCommand函数签名由caller.flash的类型推导出来如果input加了retryCount字段CLI 调用处立刻报错Electron 渲染器的trpc.flash.mutate参数也受同一 schema 约束Web 版的useMutationhook 同样如此甚至你可以用zod-to-json-schema把这个 schema 导出为 OpenAPI spec生成 Swagger UI 文档。这比手写fetch(/api/v1/flash, { method: POST, body: JSON.stringify({ port, firmware }) })安全太多——后者连firmware字段拼写错成firmare都要在运行时才发现。2.4 Next.js不止是 Web 框架更是“多端服务中枢”Next.js 在 t3code 架构里承担着三个不可替代的角色Web 应用宿主SSR/SSG 提供首屏性能App Router 支持 streaming、server actionsAPI 中枢所有端CLI/Electron/Mobile最终都通过 HTTP 调用 Next.js 的 API Routes实现统一鉴权、日志、限流Electron 主进程服务利用next dev的热重载能力让 Electron 主进程启动一个本地 HTTP server渲染器直接访问http://localhost:3000避免 Electron 自带的file://协议带来的 CORS 和模块加载限制。这里有个极易被忽略的细节Next.js 的app/api/xxx/route.ts默认运行在 Edge RuntimeVercel 环境但在本地开发和 Electron 场景下你需要强制它运行在 Node.js Runtime。否则像fs.promises.readFile、child_process.exec这些 Node.js API 会报错。解决方案是在app/api/flash/route.ts开头添加export const runtime nodejs; // 强制 Node.js Runtime export const preferredRegion home; // 本地开发时忽略 region import { NextRequest, NextResponse } from next/server; import { appRouter } from packages/core/src/trpc/router; import { createCallerFactory } from packages/core/src/trpc/caller; export async function POST(req: NextRequest) { const caller createCallerFactory(appRouter)(); const body await req.json(); try { const result await caller.flash(body); return NextResponse.json(result); } catch (err) { return NextResponse.json({ error: (err as Error).message }, { status: 400 }); } }提示runtime nodejs是 Next.js 14 的新特性旧版本需在next.config.js中配置experimental.runtime。很多开发者卡在ERR_UNSUPPORTED_ESM_URL_SCHEME错误就是因为没加这行。3. 核心模块拆解CLI、Electron、Web、Mobile 如何共享逻辑又保持独立3.1 CLI 模块如何做到“零依赖安装”和“秒级启动”CLI 工具最怕什么用户npm install -g t3code等 3 分钟期间网络波动导致失败安装完一运行t3code --version就报Cannot find module commander某个子命令如t3code resume依赖 heavy 的 AI 模型加载启动要 5 秒用户以为卡死了。t3code 的 CLI 实现原则就一条所有业务逻辑下沉到packages/coreCLI 层只做参数解析、输入校验、结果格式化。这样packages/cli的package.json可以精简到极致{ name: t3code/cli, version: 0.1.0, bin: dist/cli.js, main: dist/cli.js, types: dist/index.d.ts, dependencies: { zod: ^3.22.4, picocolors: ^1.0.0 }, devDependencies: { typescript: ^5.3.3, ts-node: ^10.9.1 } }注意没有commander、没有yargs、没有inquirer。我们用原生process.argvzod做参数解析因为commander会引入大量 polyfill比如util、events在某些 Node.js 环境如 Alpine Linux下不稳定zod的safeParse可以在解析失败时返回结构化错误比commander的help()更易定制最小化依赖 最小化安装体积 最快安装速度。packages/cli/src/cli.ts的核心逻辑#!/usr/bin/env node import { parseArgs } from node:util; // Node.js 18.11 原生 API import { z } from zod; import { flashCommand } from ./commands/flash; import { compactCommand } from ./commands/compact; // 定义 CLI 参数 schema const cliSchema z.object({ --port: z.string().optional(), --firmware: z.string().optional(), --compact: z.boolean().default(false), --help: z.boolean().default(false), }); // 解析 argv const args parseArgs({ allowPositionals: true, options: { port: { type: string, short: p }, firmware: { type: string, short: f }, compact: { type: boolean, short: c }, help: { type: boolean, short: h }, }, }); const result cliSchema.safeParse(args.values); if (!result.success) { console.error(Invalid arguments:, result.error.format()); process.exit(1); } if (result.data[--help]) { console.log(Usage: t3code [options]); process.exit(0); } if (result.data[--compact]) { await compactCommand(); } else if (result.data[--port] result.data[--firmware]) { await flashCommand(result.data[--port], result.data[--firmware]); } else { console.error(Missing required arguments: --port and --firmware); process.exit(1); }注意#!/usr/bin/env node是关键。它确保脚本在任何 POSIX 系统Linux/macOS下都能直接执行无需node cli.js。Windows 用户则通过 npm scripts 的bin字段自动注册。编译后dist/cli.js是一个纯 JavaScript 文件没有任何require(commander)这样的动态 require。用nccNext.js 官方推荐的打包工具打包ncc build src/cli.ts -o dist --no-cache --minify生成的dist/cli.js只有 127KB含 zod 和 picocolorsnpm install -g t3code/cli通常在 8 秒内完成实测 10MB/s 网络下。用户执行t3code flash --port COM3 --firmware firmware.bin从敲下回车到输出结果耗时 200ms纯逻辑执行不含模型加载。3.2 Electron 模块如何把体积从 300MB 降到 85MB并解决 localhost 通信问题Electron 应用体积大的根源有两个Chromium 内核本身占 150MBnode_modules里大量未使用的依赖比如webpack、babel、jest被错误打包进去。t3code 的 Electron 构建策略是主进程极简渲染器进程复用 Next.js Dev Server打包时只包含真正需要的模块。主进程packages/electron/main/src/index.tsimport { app, BrowserWindow, ipcMain, dialog } from electron; import * as path from path; import { createTRPCProxyClient, httpBatchLink } from trpc/client; import { AppRouter } from packages/core/src/trpc/router; // 主进程只负责 // 1. 创建窗口 // 2. 注册 IPC 接口暴露给渲染器 // 3. 处理系统事件退出、托盘、更新 function createWindow() { const win new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, preload.js), // 关键只暴露必要 API contextIsolation: true, nodeIntegration: false, }, }); // 开发时直接访问 Next.js dev server if (process.env.NODE_ENV development) { win.loadURL(http://localhost:3000); } else { // 生产时加载 Next.js build 后的静态文件 win.loadFile(path.join(__dirname, ../../web/.next/server/pages/index.html)); } } app.whenReady().then(createWindow); // Preload 脚本packages/electron/main/src/preload.ts是安全边界 // 它只暴露有限的 Electron API 给渲染器 const { contextBridge, ipcRenderer } require(electron); contextBridge.exposeInMainWorld(electronAPI, { openDialog: () ipcRenderer.invoke(dialog:open), getSerialPorts: () ipcRenderer.invoke(serial:ports), // 注意不暴露 fs、child_process 等危险 API });渲染器进程packages/electron/renderer/src/main.tsximport React from react; import ReactDOM from react-dom/client; import { QueryClient, QueryClientProvider } from tanstack/react-query; import { httpBatchLink } from trpc/client; import { AppRouter } from packages/core/src/trpc/router; import { createTRPCProxyClient } from trpc/client; import App from ./App; const queryClient new QueryClient(); // 渲染器通过 tRPC 调用 Next.js API而不是直接 require(electron) const trpc createTRPCProxyClientAppRouter({ links: [ httpBatchLink({ url: http://localhost:3000/trpc, // 关键设置 fetch 选项避免跨域问题 fetch: (input, init?) { // Electron 渲染器中fetch 默认带 credentials: include // 但 Next.js API 需要显式设置 return fetch(input, { ...init, credentials: include, }); }, }), ], }); ReactDOM.createRoot(document.getElementById(root)!).render( React.StrictMode QueryClientProvider client{queryClient} App trpc{trpc} / /QueryClientProvider /React.StrictMode );打包优化packages/electron/builder.config.tsimport { defineConfig } from electron-builder; export default defineConfig({ appId: com.t3code.app, productName: t3code, directories: { output: dist/electron, }, files: [ !node_modules/**/*, !src/**/*, !**/*.ts, !**/*.map, !**/test/**/*, !**/__tests__/**/*, // 只包含真正需要的 node_modules node_modules/zod/**/*, node_modules/trpc/**/*, node_modules/tanstack/**/*, ], // 关键禁用 asar改为 unpacked便于调试和热更新 asar: false, win: { target: [ { target: nsis, arch: [x64], }, ], }, mac: { target: default, category: public.app-category.developer-tools, }, linux: { target: AppImage, }, });注意asar: false是体积优化的关键。ASAR 归档虽然能防止代码被轻易查看但会增加启动时的解压开销且 Electron 无法按需加载 ASAR 内的文件。Unpacked 模式下electron-builder会智能剔除未引用的模块实测打包后体积macOS 85MBWindows 92MBLinux 88MB含 Chromium。关于electron localhost通信问题很多用户遇到net::ERR_CONNECTION_REFUSED是因为 Next.js dev server 没启动或 Electron 渲染器访问了错误的端口。解决方案是在package.json的scripts中定义dev:electrondev:electron: concurrently \next dev\ \wait-on http://localhost:3000 electron .\wait-on确保 Next.js 启动后再启动 Electronconcurrently让两个进程并行运行避免阻塞。3.3 Web 模块Next.js 如何成为“多端 API 网关”Next.js 的app目录结构天然适合充当 API 网关。我们把所有端共用的业务逻辑封装进packages/core然后在app/api/下创建 thin wrapper// app/api/flash/route.ts export const runtime nodejs; import { NextRequest, NextResponse } from next/server; import { createCallerFactory } from packages/core/src/trpc/caller; import { appRouter } from packages/core/src/trpc/router; // 复用 tRPC router但暴露为 REST endpoint export async function POST(req: NextRequest) { const caller createCallerFactory(appRouter)(); const body await req.json(); try { const result await caller.flash(body); return NextResponse.json(result); } catch (err) { return NextResponse.json( { error: (err as Error).message }, { status: 400 } ); } }这样做的好处Web 前端可以直接fetch(/api/flash, { method: POST, body: JSON.stringify(data) })CLI 和 Electron 渲染器也调用同一 URL所有请求都经过 Next.js 的 middleware可加 auth、rate limit、logging错误响应格式统一JSON witherrorfield前端/CLI 都能解析。更重要的是Next.js 的 Streaming 和 Server Actions 让 Web 端体验远超传统 SPA固件升级进度条用stream返回实时日志拖拽上传用useFormStateaction实现无刷新提交模型加载状态Server Component 直接读取packages/core的getModelStatus()无需额外 API 调用。3.4 Mobile 模块Capacitor vs React Native为什么选 Capacitort3code的 mobile 方案采用Capacitor而非 React Native原因很实际React Native 需要独立的 iOS/Android 工程Java/Kotlin/Swift 代码维护成本高Capacitor 把 Web 应用Next.js build 后的静态文件打包进原生容器复用 95% 的 Web 代码capacitor/core提供统一的 Plugin APICamera、Storage、Bluetooth调用方式和 Electron 的ipcRenderer一致最关键Capacitor 的Capacitor CLI可以直接集成到 TurboRepo pipeline 中。packages/mobile的结构packages/mobile/ ├── capacitor.config.ts # Capacitor 配置 ├── ios/ # Xcode 工程自动生成 ├── android/ # Android Studio 工程自动生成 └── src/ └── index.html # 指向 Next.js build 输出构建流程# 1. 构建 Next.js Web 应用 turbo run build --scopeweb # 2. 复制 build 输出到 mobile/public cp -r packages/web/.next/server/pages/* packages/mobile/public/ # 3. Capacitor 同步 npx cap sync # 4. 构建原生应用 npx cap build ios npx cap build androidCapacitor 的capacitor-community/bluetooth-le插件可以直接调用packages/core/src/serial/protocol.ts因为蓝牙通信逻辑是纯 JS不依赖原生 API。这样Web 版的串口自动识别、Electron 版的串口列表、Mobile 版的蓝牙设备扫描底层都是同一套scanDevices()函数。4. 实操全流程从初始化到打包 APK每一步的命令、参数与避坑指南4.1 初始化项目5 分钟搭建完整骨架不要用create-t3-app它已过时而是手动初始化确保你理解每一层的作用# 1. 创建 monorepo 根目录 mkdir t3code cd t3code pnpm init -y # 2. 初始化 TurboRepo pnpm add -D turbo echo { $schema: https://turborepo.org/schema.json, pipeline: { build: { dependsOn: [^build], outputs: [dist/**, .next/**] }, dev: { cache: false, persistent: true } } } turbo.json # 3. 创建 packages 目录结构 mkdir -p packages/{core,cli,electron,web,mobile} # 4. 初始化 core共享逻辑 cd packages/core pnpm init -y pnpm add zod trpc/server trpc/client cd ../.. # 5. 初始化 webNext.js cd packages/web pnpm create next-applatest . --ts --tailwind --eslint --app --src-dir # 删除默认 pages启用 app router rm -rf pages cd ../..注意pnpm是必须的。它的硬链接机制让 monorepo 中的node_modules共享避免每个 package 单独安装依赖导致磁盘爆炸。npm或yarn在大型 monorepo 中会慢 3 倍以上。4.2 配置 TypeScript 路径别名让 import packages/core 成为可能在根目录tsconfig.json中{ compilerOptions: { baseUrl: ., paths: { packages/core/*: [packages/core/src/*], packages/cli/*: [packages/cli/src/*], packages/web/*: [packages/web/src/*] } }, references: [ { path: ./packages/core/tsconfig.json }, { path: ./packages/cli/tsconfig.json }, { path: ./packages/web/tsconfig.json } ] }每个 package 的tsconfig.json继承根配置// packages/core/tsconfig.json { extends: ../../tsconfig.json, compilerOptions: { outDir: ./dist, rootDir: ./src, declaration: true, composite: true }, include: [./src/**/*], exclude: [./dist, ./node_modules] }这样packages/cli/src/commands/flash.ts就可以写import { flashFirmware } from packages/core/src/firmware/flash;而不是import { flashFirmware } from ../../../core/src/firmware/flash—— 路径别名让重构变得安全。4.3 Electron 打包 APK从 Windows/macOS 到 Android 的完整链路Electron 官方不支持 Android但t3code的 mobile 方案是 Capacitor所以“打包 APK”实际是指packages/mobile的构建。以下是详细步骤步骤 1安装 Android SDK 和 JDK下载 Android Studio包含 SDK Manager在 SDK Manager 中安装Android SDK Platform 34或最新Android SDK Build-Tools 34.0.0Android SDK Command-line Tools设置环境变量export ANDROID_HOME$HOME/Library/Android/sdk # macOS export PATH$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools步骤 2初始化 Capacitor 项目cd packages/mobile pnpm add -D capacitor/cli capacitor/core npx cap init # 问答环节 # ? What is your app name? » t3code # ? What is your app package id? » com.t3code.mobile # ? What is your web directory? » public步骤 3关联 Web 构建输出# 构建 Next.js cd ../web pnpm build cd ../mobile # 复制 build 输出 cp -r ../web/.next/server/pages/* public/ # 同步到 Android npx cap add android npx cap sync步骤 4配置 AndroidManifest.xml关键android/app/src/main/AndroidManifest.xml中添加权限uses-permission android:nameandroid.permission.BLUETOOTH / uses-permission android:nameandroid.permission.BLUETOOTH_ADMIN / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / !-- 如果需要串口通信还需 -- uses-feature android:nameandroid.hardware.usb.host /步骤 5构建 APKnpx cap build android # 生成的 APK 在android/app/build/outputs/apk/debug/app-debug.apk常见问题Execution failed for task :app:processDebugResources。原因是 Android Gradle Plugin 版本与 JDK 不兼容。解决方案在android/build.gradle中指定gradleVersion 8.4

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑