This is a starter template for the NextJS 14 app router using supabase based on shadcn-ui.
Denpendency
- NextJS 14 + Typescript + Tailwind
- Shadcn UI (Radix UI) + TimePicker + TagInput
- react-hook-form + zod
- react-i18next + zod-i18n-map
- Redux Toolkit + Redux Persist
- Supabase OAuth with PKCE flow (@supabase/ssr)
- Supabase Email Auth with PKCE flow (@supabase/ssr)
- Supabase Role-based Access Control (RBAC)
- CKEditor 5 + Supabase Upload Adapter
- PWA (Progressive Web Apps)
- NextJS with Supabase Auth
The folder and file structure is based on nextjs app router next.js project structure.
. ├── app/ # App Router │ └── api/ │ ├── auth/ # Public API for authentication │ └── v1/ # APIs that require authentication ├── components/ # React components ├── config/ # Configuration for site ├── context/ │ └── app-provider.ts # Register context provider ├── hooks/ ├── lib/ # Utility functions ├── public/ # Static assets to be served │ └── [locales]/ # Internationalization ├── queries/ # SWR for API ├── screenshots/ # Screenshots ├── store/ # Redux reducers ├── supabase/ # Supabase CLI ├── types/ ├── components.json # Shadcn UI ├── i18next.config.ts # Internationalization └── package.json # Project dependencies and scriptsSearch and replace the following text in /supabase/seed.sql:
my_bucket_idusername@example.com
Then run the sql.
Prompts:
- Would you like to use TypeScript?
Yes - Would you like to use ESLint?
Yes - Would you like to use Tailwind CSS?
Yes - Would you like to use
src/directory?No - Would you like to use App Router? (recommended)
Yes - Would you like to customize the default import alias (@/*)?
No - What import alias would you like configured?
@/*
npx create-next-app@latest . --typescriptReact Hooks for Data Fetching
npm install swrSet the current Node.js version.
node -v > .nvmrc(Optional) Generating Types if supabase cli is installed.
npm run gen-types(Optional) Generate manifest and splash screen
vim public/manifest.jsonStart the development server.
npm run devInstall Tailwind CSS
npm install --save-dev tailwindcss postcss autoprefixer npx tailwindcss init -pUsing 'clsx' or 'classnames' with 'tailwind-merge'
npm install tailwindcss-animate class-variance-authority clsx tailwind-mergeTailwindcss plugin for hide scrollbar
npm install tailwind-scrollbar-hideutils/cn.ts:
import clsx, { ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge'; export const cn = (...classes: ClassValue[]) => twMerge(clsx(...classes));Beautifully designed components that you can copy and paste into your apps. Accessible.
npx shadcn-ui@latest initAdd icon libraries
npm install lucide-react @radix-ui/react-iconsDynamically load the lucide icon component.
module.exports = { transpilePackages: ['lucide-react'], }Adding dark mode
npm install next-themesUse the add command to add components and dependencies to your project.
npx shadcn-ui@latest add npx shadcn-ui@latest add [component]The configuration for Shadcn UI is components.json.
A simple TimePicker for your Shadcn UI project.
A fully-featured tag input component built with shadcn/ui
npm install emblorInternationalization for react done right. Using the i18next i18n ecosystem.
npm install react-i18next i18next i18next-http-backendUseful for translating zod error messages.
npm install zod-i18n-mapTrans component rendering for the client side
import { useTrans } from '@/hooks/i18next' const { trans } = useTrans() export function App() { return ( <div> {trans('key', { components: { i: <i />, link1: <Link href="#" />, }, values: { what: 'world' } })} </div> ) } // translation.json // { "key": "hello <i>beautiful</i> <link1>{{what}}</link1>" }The configuration for i18next is i18next.config.ts.
React Hooks for form state management and validation (Web + React Native).
npm install react-hook-form npm install zod @hookform/resolversThe official, opinionated, batteries-included toolset for efficient Redux development.
npm install @reduxjs/toolkit react-reduxpersist and rehydrate a redux store
npm install redux-persistRun the SQL code in SQL Editor > New Query.
Generating Types
Api Docs>Introduction>Generating Types
Supabase CLI. Manage postgres migrations, run Supabase locally, deploy edge functions. Postgres backups. Generating types from your database schema.
npm install supabase --save-devLogin with your Personal Access Token:
npx supabase loginGenerate types without init
npx supabase gen types typescript --project-id "YOUR_PROJECT_ID" --schema public > types/supabase.tsGenerate types with init
npx supabase init npx supabase link --project-ref YOUR_PROJECT_ID npx supabase gen types typescript --linked > types/supabase.tspackage.json
{ "scripts": { "gen-types": "supabase gen types --lang=typescript --linked > types/supabase.ts" } }npm run gen-typesGenerating types using Supabase CLI
Install Supabase packages
npm install @supabase/supabase-js @supabase/ssrSet environment variables. Edit .env.local:
NEXT_PUBLIC_SUPABASE_URL=<your_supabase_project_url> NEXT_PUBLIC_SUPABASE_ANON_KEY=<your_supabase_anon_key>Firebase provides the tools and infrastructure you need to develop, grow, and earn money from your app.
# npm ERR! Could not resolve dependency: # npm ERR! peer firebase-admin@"^11.0.1" from firebase-frameworks@0.11.1 npm install -g firebase-tools npm install firebase firebase-admin@11.11.1Prompts:
- Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance
No - Firestore: Configure security rules and indexes files for Firestore
Yes - Functions: Configure a Cloud Functions directory and its files
No - Hosting: Configure files for Firebase Hosting and (optionally) Set up GitHub Action deploys
No - Hosting: Set up GitHub Action deploys
No - Storage: Configure a security rules file for Cloud Storage
Yes - Emulators: Set up local emulators for Firebase products
Yes - Remote Config: Configure a template file for Remote Config
No - Extensions: Set up an empty Extensions manifest
No - Frameworks: Get started with Frameworks projects.
No
firebase initCreate a new project alias.
firebase list firebase use --addInitialize the Firebase emulator.
# hosting: Port 5000 is not open on localhost (127.0.0.1,::1), could not start Hosting Emulator # Port 5000 and 7000 are taken by airplay on MacOS Monterey. firebase init emulatorsFor Mac/Linux, use the Terminal/Shell to find the Process ID (PID), then kill the process.
# Error: Could not start Hosting Emulator, port taken. lsof -ti tcp:5000 | xargs kill -9 && firebase emulators:startStart the firebase emulator.
firebase emulators:startSet the expiration of a preview channel.
firebase init hosting firebase hosting:channel:deploy preview --expires 1hStart firebase deployment.
firebase deployPowerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.
Download the files from Online Builder and unzip them into the ckeditor5 folder. However, exclude the Watchdog feature.
And run the script below:
npm install file:./ckeditor5 npm install @ckeditor/ckeditor5-react@6.2.0Upgrade ckeditor5-custom-build
cd ./ckeditor5 && npx npm-check-updates -u cd .. && npm install file:./ckeditor5Zero config PWA plugin for Next.js, with workbox
npm install next-pwaShare target browsers between different front-end tools, like Autoprefixer, Stylelint and babel-preset-env
npm install browserslistA collection of essential TypeScript types
npm install type-festSvg react icons of popular icon packs
npm install react-iconsA modern JavaScript utility library delivering modularity, performance, & extras.
npm install lodash @types/lodashDay.js 2kB immutable date-time library alternative to Moment.js with the same modern API
npm install dayjsSend e-mails with Node.JS
npm install nodemailer npm install --save-dev @types/nodemailerJsonWebToken implementation for node.js
npm install jsonwebtoken @types/jsonwebtokenSlugifies a string
npm install slugifyGenerate massive amounts of fake data in the browser and node.js
npm install @faker-js/fakerVector (*.svg) country flag icons in 3:2 aspect ratio.
npm install country-flag-iconsA small, fast and rich-API browser/platform/engine detector for both browser and node.
npm install bowserFlatten/unflatten nested Javascript objects
npm install flatESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code.
npm install --save-dev eslint eslint-plugin-react eslint-plugin-react-hooks npm install --save-dev @next/eslint-plugin-next npm install --save-dev eslint-plugin-import eslint-import-resolver-typescript npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin npm install --save-dev prettier eslint-plugin-prettier eslint-config-prettier npm install --save-dev eslint-plugin-tailwindcss prettier-plugin-tailwindcssEdit next.config.js:
module.exports = { eslint: { // Warning: This allows production builds to successfully complete even if // your project has ESLint errors. ignoreDuringBuilds: true, }, }Find and fix problems in your JavaScript code.
npx eslint ./app npx eslint --fix ./{app,components,context,hooks,lib,types,utils}To format a file in-place
npx prettier --check "./app/**/*.{ts,tsx}" npx prettier --write "./{app,components,context,hooks,lib,types,utils}/**/*.{ts,tsx}"Generating Database Types
Api Docs > Introduction > Generating Types
Edit next.config.js:
module.exports = { reactStrictMode: true, swcMinify: true, }Edit packages.json:
{ "scripts": { "clean:dir": "rm -rf node_modules", "clean:cache": "npm cache clean --force", "clean": "npm run clean:dir && npm run clean:cache", "reinstall": "npm run clean && npm install" }, }To enable Turbopack. Edit packages.json:
{ "scripts": { "dev": "next dev --turbo" } }After cleaning the directories and cache, install the dependency packages.
npm run --force reinstallRegister your provider in context/app-provider.tsx.
import { Provider } from '@/context/provider' const providers = [Provider]Register your redux store in store/root-reducer.ts.
import reducer from './features/slice' const rootReducer = combineReducers({ name: reducer, })Client side
import { fetcher } from '@/lib/utils' const onSubmit = async (formValues: FormValues) => { const { data, error } = await fetcher<FetchData>('https://...', { method: 'POST', body: JSON.stringify(formValues), }) }Server side: route.ts
import { NextResponse, type NextRequest } from 'next/server' export async function POST(request: NextRequest) { const body = await request.json() return NextResponse.json({ body }) }Client side
import { fetcher } from '@/lib/utils' const onSubmit = async (formValues: FormValues) => { const formData = new FormData() formData.append('email', formValues?.email) const { data, error } = await fetcher<FetchData>('https://...', { method: 'POST', body: formData, }) }Server side: route.ts
import { NextResponse, type NextRequest } from 'next/server' export async function POST(request: NextRequest) { const formData = await request.formData() const email = formData.get('email') as string return NextResponse.json({ email }) }import { Button } from '@/components/ui/button' export function Component() { const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false) const onSubmit = async (formValues: FormValues) => { setIsSubmitting(true) try { const { data, error } = await fetch('https://...').then((res) => res.json()) if (error) throw new Error(error?.message) // ... } catch (e: unknown) { console.error((e as Error)?.message) } finally { setIsSubmitting(false) } } return <Button disabled={isSubmitting}>Submit</Button> }CJK (Chinese - Japanese - Korean)
/[一-龥ぁ-ゔァ-ヴー々〆〤ㄱ-ㅎㅏ-ㅣ가-힇]+/gcn (Chinese)
/[一-龥]+/gjp (Japanese)
/[ぁ-ゔァ-ヴー々〆〤]+/gko (Korean)
/[ㄱ-ㅎㅏ-ㅣ가-힇]+/gDeploy app to Vercel
vercel deployDeploying Static Exports next.config.js:
module.exports = { output: 'export', exportTrailingSlash: true, assetPrefix: '/out', }Dependency packages in production for deployment on Vercel hosting.
npm install @vercel/analytics(Optional) Vercel Speed Insights package
npm install @vercel/speed-insights(Optional) Image Optimization
Usage image component
import Image from 'next/image' export default function Page() { return <Image /> }Set image element rules in .eslintrc.js:
module.exports = { rules: { '@next/next/no-img-element': 'warn', } }...
