{"id":26258980,"url":"https://github.com/ecarry/youtube-clone","last_synced_at":"2025-04-30T04:48:20.920Z","repository":{"id":276052272,"uuid":"928045206","full_name":"ECarry/youtube-clone","owner":"ECarry","description":"Code With Antonio: Ultimate Next 15 Course: Build a YouTube Clone (2025)","archived":false,"fork":false,"pushed_at":"2025-03-23T13:22:54.000Z","size":1119,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-30T04:48:06.760Z","etag":null,"topics":["betterauth","deepseek","drizzle-orm","neondb","nextjs15","shadcn-ui","trpc"],"latest_commit_sha":null,"homepage":"https://youtube-clone-orcin-iota.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ECarry.png","metadata":{"files":{"readme":"README-zh.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-06T00:58:06.000Z","updated_at":"2025-04-05T19:46:59.000Z","dependencies_parsed_at":"2025-02-06T02:35:41.149Z","dependency_job_id":"aa08e5b0-ec91-420e-b2e9-f25fbbb48f25","html_url":"https://github.com/ECarry/youtube-clone","commit_stats":null,"previous_names":["ecarry/youtube-clone"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ECarry%2Fyoutube-clone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ECarry%2Fyoutube-clone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ECarry%2Fyoutube-clone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ECarry%2Fyoutube-clone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ECarry","download_url":"https://codeload.github.com/ECarry/youtube-clone/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251644826,"owners_count":21620630,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["betterauth","deepseek","drizzle-orm","neondb","nextjs15","shadcn-ui","trpc"],"created_at":"2025-03-13T22:15:32.390Z","updated_at":"2025-04-30T04:48:20.884Z","avatar_url":"https://github.com/ECarry.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# YouTube 克隆项目 🎥\n\n一个构建全功能 YouTube 克隆应用的完整指南。\n\n📺 [观看完整教程 @CodeWithAntonio](https://www.youtube.com/watch?si=oP2_MMRY_Jc61GSn\u0026v=ArmPzvHTcfQ\u0026feature=youtu.be)\n\n## 核心功能 🚀\n\n### 视频管理\n\n- 🎬 视频基础设施和存储（由 MuxHQ 提供支持）\n- 📝 自动视频转录\n- 🖼️ 智能缩略图生成\n- 🤖 AI 驱动的后台任务（使用 Upstash）\n\n### 用户功能\n\n- 📊 创作者工作室及数据分析\n- 🗂️ 播放列表管理系统\n- 💬 互动评论\n- 👍 点赞和订阅系统\n- 🎯 观看历史记录\n- 🔐 用户认证（由 Clerk 提供支持）\n\n### 技术栈 💻\n\n#### 核心技术\n\n- 🚀 Next.js 15\n- ⚛️ React 19\n- 🔄 tRPC（类型安全的 API）\n\n#### 数据库和存储\n\n- 🗄️ PostgreSQL（Neon 数据库）\n- 🔍 DrizzleORM\n\n#### 用户界面\n\n- 💅 TailwindCSS\n- 🎨 shadcn/ui\n- 📱 响应式设计\n\n# 环境搭建 ⚙️\n\n- 配置环境\n\n  - 运行时（Node.js, Bun）\n  - 包管理器（npm, pnpm, bun）\n\n- 为什么选择 Bun？\n  - 轻松运行带有 ES6 导入的 TypeScript 脚本\n  - 减少与 React 19 相关的依赖问题\n  - 基本 Bun 命令说明\n    - bun add === npm install\n    - bunx === npx\n\n# 数据库设置 🌵\n\n- 创建 Postgres 数据库（[neon](https://www.neon.tech)）\n- 配置 Drizzle ORM\n- 创建用户数据模型\n- 迁移数据库变更\n- 学习使用 drizzle-kit\n\n## 为什么选择 Drizzle ORM？\n\n- 唯一同时支持关系型和类 SQL 查询 API 的 ORM\n- 默认支持 Serverless\n- 促使我们更好地理解查询\n\n### Prisma 风格的查询\n\n```javascript\nconst result = await db.query.users.findMany({\n  with: {\n    posts: true,\n  },\n});\n```\n\n### SQL 风格的查询\n\n```javascript\nconst result = await db\n  .select()\n  .from(countries)\n  .leftJoin(cities, eq(cities.countryId, countries.id))\n  .where(eq(cities.id, 1));\n```\n\n# Webhook 同步 🌈\n\n- 创建 ngrok 账号（或其他本地隧道方案）\n- 获取静态域名\n- 添加脚本以同时运行本地隧道和应用\n- 创建用户 webhook\n- 在 Clerk 仪表板中连接 webhook\n\n# tRPC 设置 🥥\n\n## 为什么选择 tRPC？\n\n- 端到端类型安全\n- 熟悉的 hooks（useQuery, useMutation, useInfiniteQuery）\n- v11 支持认证预获取\n\n## 为什么不选择其他框架（如 Hono.js）？\n\n- 不能预取认证查询(prefetch authenticated queries)\n\n### 1.核心问题-认证状态的处理\n\n```typescript\n// tRPC 的方式\n// 服务器组件可以直接访问认证状态\nasync function ProtectedPage() {\n  // 可以在服务器端直接预取需要认证的数据\n  const userData = await trpc.auth.getUser.prefetch();\n  return \u003cComponent data={userData} /\u003e;\n}\n\n// Hono + React Query 的方式\n// ❌ 不能在服务器组件中使用\n(\"use client\");\nfunction ProtectedPage() {\n  // 认证查询只能在客户端进行\n  const { data } = useQuery({\n    queryKey: [\"user\"],\n    queryFn: () =\u003e client.getUser(),\n  });\n}\n```\n\n### 2.预取的时机\n\n- tRPC 可以在服务器端完成认证检查和数据预取\n- Hono + React Query 必须等到客户端代码执行才能获取认证状态\n\n### 3.实际影响\n\n```typescript\n// tRPC 方式：一次请求完成所有操作\nasync function DashboardPage() {\n  // ✅ 服务器端同时处理认证和数据获取\n  const [user, posts, notifications] = await Promise.all([\n    trpc.auth.getUser.prefetch(),\n    trpc.posts.list.prefetch(),\n    trpc.notifications.list.prefetch(),\n  ]);\n  return \u003cDashboard user={user} posts={posts} notifications={notifications} /\u003e;\n}\n\n// Hono + React Query 方式：需要多次往返\n(\"use client\");\nfunction DashboardPage() {\n  // ❌ 需要先获取认证状态\n  const { data: auth } = useQuery([\"auth\"]);\n  // ❌ 然后才能获取需要认证的数据\n  const { data: posts } = useQuery([\"posts\"], {\n    enabled: !!auth, // 依赖认证状态\n  });\n  // ❌ 瀑布式的数据获取\n  const { data: notifications } = useQuery([\"notifications\"], {\n    enabled: !!auth,\n  });\n}\n```\n\n#### 影响和后果\n\n1. 性能影响\n\n- 额外的网络往返\n- 瀑布式的数据加载\n- 首屏加载时间增加\n\n2. 用户体验\n\n- 可能出现加载闪烁\n- 内容加载延迟\n- 分阶段渲染\n\n3. 开发复杂性\n\n```typescript\n// tRPC：简洁明了\nexport const createRouter = t.router({\n  protectedRoute: t.procedure\n    .use(isAuthed) // 中间件处理认证\n    .query(async ({ ctx }) =\u003e {\n      // 已认证的查询\n      return ctx.db.getData();\n    }),\n});\n\n// Hono：需要额外处理\napp.use(\"/api/*\", async (c, next) =\u003e {\n  // 需要在每个需要认证的路由中处理\n  const auth = await authenticate(c);\n  if (!auth) {\n    return c.json({ error: \"Unauthorized\" }, 401);\n  }\n  await next();\n});\n```\n\n## Why prefetch?\n\n- \"render as you fetch\" concept\n- leverage RSCs as \"loaders\"\n- faster load time\n- parallel data loading\n\n### Render as you fetch\n\n这是一种现代的数据获取模式，不同于传统的 \"fetch-on-render\" 或 \"fetch-then-render\"。\n\n```typescript\n// 传统方式 (fetch-on-render)\nfunction OldComponent() {\n  const [data, setData] = useState(null);\n\n  useEffect(() =\u003e {\n    // ❌ 等待渲染后才开始获取数据\n    fetchData().then(setData);\n  }, []);\n}\n\n// Render as you fetch (使用 tRPC)\nasync function NewComponent() {\n  // ✅ 数据获取与渲染同时开始\n  const dataPromise = trpc.data.query.prefetch();\n\n  // 可以立即开始渲染UI框架\n  return (\n    \u003cSuspense fallback={\u003cLoading /\u003e}\u003e\n      \u003cAsyncContent promise={dataPromise} /\u003e\n    \u003c/Suspense\u003e\n  );\n}\n```\n\n### RSCs as \"loaders\"\n\n利用 React Server Components (RSCs) 作为数据加载器：\n\n```typescript\n// Server Component 作为数据加载器\nasync function BlogPostLoader({ id }: { id: string }) {\n  // ✅ 在服务器端直接加载数据\n  const post = await trpc.posts.getPost.fetch({ id });\n  const comments = await trpc.comments.list.fetch({ postId: id });\n\n  return (\n    \u003carticle\u003e\n      \u003cPostContent post={post} /\u003e\n      \u003cSuspense fallback={\u003cCommentsSkeleton /\u003e}\u003e\n        \u003cComments initialData={comments} /\u003e\n      \u003c/Suspense\u003e\n    \u003c/article\u003e\n  );\n}\n\n// 客户端组件接收预加载的数据\n(\"use client\");\nfunction Comments({ initialData }) {\n  // ✅ 使用预加载的数据，避免客户端重新请求\n  const { data } = useQuery({\n    queryKey: [\"comments\"],\n    queryFn: () =\u003e trpc.comments.list.query(),\n    initialData,\n  });\n}\n```\n\n### Faster load time\n\n通过并行数据加载和服务器端预取实现更快的加载时间：\n\n```typescript\n// 快速加载时间示例\nasync function DashboardPage() {\n  // ✅ 并行预取多个数据源\n  const [userData, posts, analytics, notifications] = await Promise.all([\n    trpc.users.getProfile.prefetch(),\n    trpc.posts.list.prefetch(),\n    trpc.analytics.summary.prefetch(),\n    trpc.notifications.recent.prefetch(),\n  ]);\n\n  return (\n    \u003cLayout\u003e\n      \u003cUserProfile data={userData} /\u003e\n      \u003cRecentPosts posts={posts} /\u003e\n      \u003cAnalyticsDashboard data={analytics} /\u003e\n      \u003cNotificationPanel notifications={notifications} /\u003e\n    \u003c/Layout\u003e\n  );\n}\n```\n\n### Parallel data loading\n\n通过并行数据加载实现更快的加载时间和更好的用户体验：\n\n```typescript\n// ❌ 瀑布式数据加载 (不好的方式)\nasync function SerialLoadingPage() {\n  // 串行加载，每个请求都要等待前一个完成\n  const user = await getUser();\n  const posts = await getUserPosts(user.id);\n  const comments = await getPostComments(posts[0].id);\n}\n\n// ✅ 并行数据加载 (好的方式)\nasync function ParallelLoadingPage() {\n  // 1. 定义所有数据获取操作\n  const queries = {\n    user: trpc.users.getUser.prefetch(),\n    posts: trpc.posts.list.prefetch(),\n    categories: trpc.categories.list.prefetch(),\n    tags: trpc.tags.list.prefetch(),\n  };\n\n  // 2. 并行执行所有查询\n  const data = await Promise.all(Object.values(queries));\n\n  // 3. 使用 Suspense 优化加载体验\n  return (\n    \u003cLayout\u003e\n      \u003cSuspense fallback={\u003cUserSkeleton /\u003e}\u003e\n        \u003cUserSection data={data.user} /\u003e\n      \u003c/Suspense\u003e\n      \u003cSuspense fallback={\u003cPostsSkeleton /\u003e}\u003e\n        \u003cPostsSection data={data.posts} /\u003e\n      \u003c/Suspense\u003e\n    \u003c/Layout\u003e\n  );\n}\n```\n\n### 这些特性的优势\n\n1. 更好的性能\n\n- 减少总加载时间\n- 避免串行请求\n- 优化首屏加载\n\n2. 更好的用户体验\n\n- 渐进式加载\n- 更快的交互响应\n- 更流畅的页面转换\n\n3. 更好的开发体验\n\n- 声明式数据获取\n- 类型安全\n- 更容易的错误处理\n\n4. 更好的资源利用\n\n- 减少服务器负载\n- 优化带宽使用\n- 更好的缓存利用\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecarry%2Fyoutube-clone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fecarry%2Fyoutube-clone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecarry%2Fyoutube-clone/lists"}