发布时间

怎样用Next.js完成全栈做项目来赚钱

作者
  • avatar
    作者名字
    Kavin Wang
    Twitter

本文为网上的一个帖子的翻译,原文参考本文末。

大家好!
我前几个礼拜做了一个程序,竟然赚了点钱,嗯,我觉得有有必要给大家说一说咋做的。

先说明一下啊:

之前我是没用过其它任何框架的,大约一年前吧,我用Next.js这个框架写下了我第一行代码。用的时候遇到很多问题,也多次在reddit和twitter上抱怨过Next.js的性能,也觉得Next.js处理一些东西时过于复杂。比如服务端组件经常让我感到比较难用(老实讲,大部分情况是因为我想当然的认为我使用openai的key写的react程序是运行在客户端的😂)。不过,回想起来,我实际上还是非常喜欢它滴。

言归正传

  • 语言:Typescript。 无脑选择这个就对啦。我把所有的需要参数的函数都是用一个dictrionary的名值对的方法传入的。
  • 框架:NextJS (Server Actions) 。我几乎在任何地方都是用server action,不仅仅是表单提交,还是可变数据。除非迫不得已,我把所有的操作都用server action来代替API router使用。
  • Styling:TailwindCSS。继续无脑选择就可以了。不过,我添加一些我自己定义的class进去。如下:
.shadow-neumorphic {
  @apply shadow-[5px_5px_30px_rgba(190,190,190,0.15),-5px_-5px_30px_rgba(255,255,255,0.15)];
}

.flex-center {
  @apply flex items-center justify-center;
}

.flex-between {
  @apply flex items-center justify-between;
}

.test-red {
  @apply border-2 border-red-500;
}

.test-blue {
  @apply border-2 border-blue-500;
}

.test-green {
  @apply border-2 border-green-500;
}

.test-purple {
  @apply border-2 border-purple-500;
}
  • Icons:Phosphor Icons (through React Icons) 。我觉得Phosphor Icons特别漂亮。但是这个东西在服务端组件上有些问题,因此,还是选择了React Icons。
const nextConfig = {
//...
  modularizeImports: {
    'react-icons/?(((\\w*)?/?)*)': {
      transform: '@react-icons/all-files/{{ matches.[1] }}/{{ member }}',
      skipDefaultConversion: true,
    },
  },
//...
}

module.exports = nextConfig
  • UI components:使用Shadcn/ui。用起来特别爽,活干的特别快。需要的地方抄就可以了,别考虑复用的问题。

  • Animations:Framer Motion。用于创建动画的有趣、高度可定制的库。参考下我是怎样用的,下面的代码是一个浮动菜单。

//Parent of this component is a div with "fixed" className

'use client'
import { motion, useTransform, useScroll, useSpring, useMotionValueEvent } from 'framer-motion'

const ScrollFloatingAnimation = ({ children }: { children: React.ReactNode }) => {
  const { scrollYProgress } = useScroll()

  const smoothProgress = useSpring(scrollYProgress, {
    mass: 0.5,
    stiffness: 50,
    damping: 20,
    restDelta: 0.001,
  })

  const y = useTransform(smoothProgress, (value) => {
    return (value - scrollYProgress.get()) * 100
  })

  return (
    <motion.div
      initial={{ opacity: 0, y: -20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ type: 'spring', stiffness: 50 }}
      style={{ y }}
      className="flex-center w-full">
      {children}
    </motion.div>
  )
}

export default ScrollFloatingAnimation
  • 数据库:Supabase。弄起来特别简单,竟然还提供他们自己实现的ORM。
  • 认证: upabase Auth。和他的数据库集成的非常好,零配置,炒鸡好用。
  • 部署:Vercel。 零配置,无限扩展。不用考虑太多项目管理,命令行一键搞定。
  • CDN:Cloudflare。攻击防护,网页加载很快,体验特别好。
  • Product Analytics:Posthog。特别好用,是Google Analytics的好用版。
  • 存储:S3。便宜好用。还有谁不用这个的?啊!
  • 后台自动job:Trigger.dev。BullMQ的绝佳severless替代方案。
  • Async State Manager:React Query(😮‍💨)。
  • State Manager:Zustand (没错 RQ 和 Zustand我都用)
  • Email(Transactional, Inbound and Broadcast):Postmark。
  • 支付Payments:Stripe。
  • Schema validation:Zod。
  • Markdown:Next/mdx。
  • 日期功能:Date-fns。作者喜欢,我不喜欢😂。
  • AI:Replicate + OpenAI + StableDiffusion + ElevenLabs。完全够了,其它的靠边站。
  • 视频:Remotion.

有个事情得说一下,对于状态管理,当需要异步状态时使用React Query,应用内状态时使用Zustand。

  • Query 下面是我从数据库中读取数据的代码(每一个RQ状态都使用一个Server Action)。
import { useQuery } from '@tanstack/react-query'
import {
  getCreations,
  getCredits,
  getDocuments,
  getProducts,
  getProject,
} from '../server-actions/'
import { getFeatures, getSubscription } from '../server-actions/stripe'

export const useDocuments = () => {
  return useQuery({
    queryKey: ['documents'],
    queryFn: () => getDocuments(),
  })
}

export const useCreations = () => {
  return useQuery({
    queryKey: ['creations'],
    queryFn: () => getCreations(),
  })
}

export const useSubscription = () => {
  return useQuery({
    queryKey: ['subscription'],
    queryFn: () => getSubscription(),
  })
}

export const useFeatures = () => {
  return useQuery({
    queryKey: ['features'],
    queryFn: () => getFeatures(),
  })
}

export const useCredits = () => {
  return useQuery({
    queryKey: ['credits'],
    queryFn: () => getCredits({}),
  })
}

export const useProject = ({ projectID }: { projectID: string }) => {
  return useQuery({
    queryKey: ['project', projectID],
    queryFn: () => getProject({ projectID }),
  })
}

export const useProducts = () => {
  return useQuery({
    queryKey: ['products'],
    queryFn: () => getProducts(),
  })
}
  • Zustand:我用Zustand保存我在视频编辑器中的所有状态。比如程序缩放啥的。

原文: I finally made NextJS app that earned money ($650) - a full stack breakdown