create-t3-app
create-t3-app copied to clipboard
Extended version of create-t3-app to make it even faster to start a project.
create-T3-app with extra tools/config out of the box
create-t3-app is one of the fastest and easiest way to scaffold fullstack app. This version make it even faster for my case (and maybe yours). ๐๐จ
Step to follow:
-
pnpm install
- Rename next-env to next-env.d.ts
- Rename .env.example to .env and fill the value 3.1 Local database 3.2 Google Oauth
- Deploy ๐ ๐ 4.1 Planetscale 4.2 Vercel
More detail about how we modify the code:
- ๐งน Linting & Formatting
- โ๏ธ Git hooks
- ~~Pre-commit~~
- Commit message
- ~~Commit emoji~~
- ~~Pre-push~~
- ๐ Optimization
- Bundle size
- CSS
- ๐ Deployment
- MySQL on PlanetScale
- Google OAuth
- Vercel
- ๐ช Others
- Fonts
- Favicon
- Animation
- SASS
- Tailwind config
-
Path aliases
Interesting Discussion
- ๐ก๏ธ Bleeding edge tech
- ORM replacement
- DB replacement
Linting & Formatting ๐งน
better code [^1] without the frustration of config.
Install prettier with the config & plugin for eslint & tailwind
pnpm i -D prettier eslint-config-prettier eslint-plugin-prettier prettier-plugin-tailwindcss
โ Confuse about plugin vs config? Read this and this.
โ Why don't we use stylelint also?
โ Tailwind is more than enough. Use arbitrary value & custom style.
Here is my experience:
I make component animation on css and realize that you can move it to tailwind custom style.
I use scss on svg animation and realize css is not for this kind of job. You will screw up really fast (sarah drasner said). I move using animation library instead (more below).
Add prettier config file prettier.config.cjs
module.exports = {
trailingComma: 'es5',
useTabs: true,
tabWidth: 2,
semi: false,
singleQuote: true,
bracketSpacing: false,
jsxSingleQuote: true,
plugins: [require('prettier-plugin-tailwindcss')],
tailwindConfig: './tailwind.config.cjs',
}
Extend eslint config .eslint.json
{
- "plugins": ["@typescript-eslint"],
+ "plugins": ["@typescript-eslint", "prettier"],
"extends": [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
+ "prettier"
],
+ "rules": {
+ "prettier/prettier": "warn"
+ }
}
You can use prettier only on formatting or also give linting error/ warning. For my chase warn me.
Add more plugin if you need it
I personally love unicorn plugin.
Lint & format all of your file
pnpm prettier --write .
pnpm eslint .
Git hooks โ๏ธ
~~better [^1] and more exciting git experience~~. It can slow you down and it is. Here is why. Move pre-commit & pre-push on CI (vercel status check)
๐งน ~~Pre-commit~~
โ You don't need it. But if your need it, add it by yourself.
Add husky to the project
pnpm dlx husky-init && pnpm i
Install lint-staged
pnpm i -D lint-staged
Add config file .lintstagedrc
{
"*.{js,jsx,cjs,ts,tsx}": "eslint --fix",
"*.{md,json}": "prettier --write"
}
Run lint-staged on pre-commit hook
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
- npm test
+ pnpm lint-staged
If the log message doesn't show correctly, see this issue
๐จ Commit message
โ Give clear message by following the convention
Install commitlint
pnpm install -D @commitlint/cli @commitlint/config-conventional
Add config file
commitlint.config.cjs
module.exports = {
extends: ['@commitlint/config-conventional'],
}
Add to commit-message hook
pnpm dlx husky add .husky/commit-msg "pnpm commitlint --edit \"\$1\""
Test by making a commit
git commit -m "foo: bar"
๐คฏ ~~Commit emoji~~
โ Who don't like emoji??
Install gitmoji
pnpm i -g gitmoji-cli
Install gitmoji config for commitlint
pnpm i -D commitlint-config-gitmoji
Update commitlint config file
module.exports = {
- extends: ['@commitlint/config-conventional'],
+ extends: ['gitmoji'],
+ rules: {
+ 'header-max-length': [0, 'always', 100],
+ 'scope-case': [0, 'always', 'pascal-case'],
+ },
}
Commit using gitmoji
gitmoji -c
๐๏ธ ~~Pre-push~~
โ Clean doesn't mean it's not break
pnpm dlx husky add .husky/pre-push "pnpm build"
Hosting provider usually charge money if you exceed the build time limit. It can save you some time.
Optimization ๐
Don't bring unnecessary thing in your baggage
๐ฆ Bundle Analyzer
โ Consider package bundle size before add it to your arsenal.
Install bundle analyzer
pnpm -i -D @next/bundle-analyzer
Edit next.config.cjs
+ import bundleAnalyzer from '@next/bundle-analyzer'
+ const withBundleAnalyzer = bundleAnalyzer({
+ enabled: process.env.ANALYZE === 'true',
+ })
function defineNextConfig(config) {
- return config
+ return withBundleAnalyzer(config)
}
Add bundle analyzer build script
+ "build-stats": "ANALYZE=true pnpm build"
Run build with bundle analyzer
pnpm build-stats
You can also check using bundle size using bundlephobia.
๐งฐ CSS
โ Optimize tailwind on production
Minify CSS using cssnano
pnpm -i -D cssnano
Edit postcss.config.cjs
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
+ ...(process.env.NODE_ENV === 'production' ? {cssnano: {}} : {}),
},
}
Going Live ๐
Why wait so long to go to the moon?
๐ช MySQL on PlanetScale
โ Sit with ease in case your app suddenly become a startup. Watch this interview
I use both local database & PlanetScale branch database for development. Depend on your need.
For local I use prisma migrate dev
For remote I use prima db push
Read this for the differences.
Local MySQL server
For MySQL installation follow guide.
Set database url on .env
DATABASE_URL=mysql://user:password@localhost:3306/database_name
Migrate local database (better wait after planet scale setup)
pnpm prisma migrate dev
PlanetScale setup
Ignore prisma migration file in .gitignore
/prisma/migrations/*
Follow this instruction and you are good to go.
Code mods:
prisma.schema
datasource db {
url = env("DATABASE_URL")
+ relationMode = "prisma"
model Account {
...
+ @@index([userId])
}
model Session {
...
+ @@index([userId])
}
Replace your DATABASE_URL on .env
with url that you get from PlanetScale
Add SSL certificate to database url to enable secure connection. For SSL certificate location, see this docs.
?ssl={"rejectUnauthorized":true}&sslcert=/etc/pki/tls/certs/ca-bundle.crt
To establish secure connection from local server with branch database, add env below.
MYSQL_ATTR_SSL_CA=/etc/ssl/certs/ca-certificates.crt
๐ Google OAuth
โ Who doesn't have google account?
Create new project
Configure OAuth consent screen
Your vercel preview branch domain will not be allowed to access the OAuth consent screen except your add it. So my workaround would be to create a development branch, add a new domain (for preview) on vercel and attach it to the development branch. On the authorized domains section, add both of your domains.
your-domain.vercel.app
your-preview-domain.vercel.app
Create credentials
Add "Authorized JavaScript origins" with base url
http://localhost:3000
https://your-domain.vercel.app
https://your-preview-domain.vercel.app
Add "Authorized redirect URIs" with callback url
http://localhost:3000/api/auth/callback/google
https://your-domain.vercel.app/api/auth/callback/google
https://your-preview-domain.vercel.app/api/auth/callback/google
Add google credential to .env
+ GOOGLE_CLIENT_ID = 'Your Client ID'
+ GOOGLE_CLIENT_SECRET = 'Your Client Secret'
Add google env to schema.mjs
export const serverSchema = z.object({
...
+ GOOGLE_CLIENT_ID: z.string(),
+ GOOGLE_CLIENT_SECRET: z.string(),
})
Add Google provider & enable jwt on [..nextauth].ts
+ import GoogleProvider from 'next-auth/providers/google'
callbacks: {
session({session, user}) {
...
},
+ async jwt({token}) {
+ return token
+ },
},
providers: [
+ GoogleProvider({
+ clientId: env.GOOGLE_CLIENT_ID,
+ clientSecret: env.GOOGLE_CLIENT_SECRET,
+ }),
...
],
Enable jwt callback (required)
callbacks: {
session({session, user}) {
...
},
+ async jwt({token}) {
+ return token
+ },
},
๐บ Vercel
โ Except you like to complicate things
Add the env for each environment & deploy
Add your live url as next auth url on .env
+ NEXTAUTH_URL=https://your-domain.vercel.app
Other helpful things ๐ช
Trivially important
๐ NextJS custom Document
Create custom document _document.tsx
on pages directory
import {Html, Head, Main, NextScript} from 'next/document'
export default function Document() {
return (
<Html>
<Head></Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
๐ ต Fonts
โ There are a lot of curated font pairing ready to pick.
Pick font pairing from two of the most useful collection from heyreliable.com and pagecloud.com. You can also filter by the style that match your app.
๐ก Steal font combo from your favorite website.
Go to google font and search those fonts.
Select the specimen that you will use. Remember about performance! I recommend pick three font weight and the italic version of each weight.
Add the font link inside <Head>
component on _document.tsx
<Head>
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
+ <link
+ href="https://fonts.googleapis.com/css2?family=Hind:wght@400;600&family=Montserrat:ital,wght@0,400;0,600;0,800;1,400;1,600;1,800&display=swap"
+ rel="stylesheet"
+ />
</Head>
Extend tailwind config with the font family
theme: {
extend: {
fontFamily: {
+ heading: ['Montserrat', 'sans-serif'],
+ body: ['Hind', 'sans-serif'],
},
},
},
You can apply it directly to the tag if needed by changing styles/global.css
@layer base {
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-heading;
}
p {
@apply font-body;
}
}
โญ Favicon
โ Just get it correctly.
Prepare your svg icon
Go to realfavicongenerator.net
Adjust & generate favicon
Download & put on public directory
Copy generated link to head on _document.tsx
<Head>
...
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
+ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
+ <link rel="manifest" href="/site.webmanifest" />
+ <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
+ <meta name="msapplication-TileColor" content="#2d89ef" />
+ <meta name="theme-color" content="#ffffff" />
<Head />
๐ Animation
โ Steal user attention & help them navigate.
These css animation collection very useful to make your website stand out
Auto animate also really helpful for element transition.
For svg animation use GSAP. Sarah Drasner and other pro recommend it because it's the most mature and reliable library.
๐จ Tailwind config
โ Small details fixes.
Additional default override. For what? read this
@layer base {
html {
-webkit-tap-highlight-color: transparent;
}
}
@layer utilities {
.pb-safe {
padding-bottom: env(safe-area-inset-bottom);
}
}
๐ SASS
โ Managing keyframe will ruin your entire day.
You will mess up very quickly if you don't use variables for handling keyframe. But we don't want JS yet for this simple animation.
- Attention seeker & Transition > SASS
- Interaction & complex SVG > JS library
Install sass
pnpm i -D sass
Add script to watch & debug sass
"scripts": {
...
+ "sass-watch": "sass --watch src/styles:css"
},
Ignore output file
+ # sass watch output
+ /css
Add typescript-plugin-css-modules for autocompletion
pnpm i -D typescript-plugin-css-modules
Update tsconfig
"compilerOptions": {
+ "plugins": [{ "name": "typescript-plugin-css-modules" }]
}
Add to vscode config
+ "typescript.tsserver.pluginPaths": ["typescript-plugin-css-modules"]
๐ฃ Path Aliases
โ Stop playing guess game while importing module
Add base path & path aliases on tsconfig.json
+ "baseUrl": "src",
+ "paths": {
+ "@components/*": ["components/*"],
+ "@api/*": ["pages/api/*"],
+ "@pages/*": ["pages/*"],
+ "@animation/*": ["styles/animation/*"],
+ "@styles/*": ["styles/*"],
+ "@utils/*": ["utils/*"],
+ "@server/*": ["server/*"],
+ "@images/*": ["../public/images/*"]
+ },
๐ก๏ธ ~~Bleeding~~ edge tech
Cool tech should not make you bleeding
These is only for exploration & discussion.
ORM Replacement
Kysely provide end-to-end type-safety but also edge-first approach to ORM replacement
DB Replacement
Legacy databaase is awesome. PlanetScale is super awesome.
Next to cover
- vscode extension
[^1]: more readable & manageable also prevent error