Initial commit

This commit is contained in:
2026-01-08 01:04:26 +00:00
committed by GitHub
commit 3ebf60d5dd
122 changed files with 39020 additions and 0 deletions
@@ -0,0 +1,15 @@
import { setRequestLocale } from 'next-intl/server';
export default async function CenteredLayout(props: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await props.params;
setRequestLocale(locale);
return (
<div className="flex min-h-screen items-center justify-center">
{props.children}
</div>
);
}
@@ -0,0 +1,30 @@
import type { Metadata } from 'next';
import { SignIn } from '@clerk/nextjs';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import { getI18nPath } from '@/utils/Helpers';
type ISignInPageProps = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata(props: ISignInPageProps): Promise<Metadata> {
const { locale } = await props.params;
const t = await getTranslations({
locale,
namespace: 'SignIn',
});
return {
title: t('meta_title'),
description: t('meta_description'),
};
}
export default async function SignInPage(props: ISignInPageProps) {
const { locale } = await props.params;
setRequestLocale(locale);
return (
<SignIn path={getI18nPath('/sign-in', locale)} />
);
};
@@ -0,0 +1,30 @@
import type { Metadata } from 'next';
import { SignUp } from '@clerk/nextjs';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import { getI18nPath } from '@/utils/Helpers';
type ISignUpPageProps = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata(props: ISignUpPageProps): Promise<Metadata> {
const { locale } = await props.params;
const t = await getTranslations({
locale,
namespace: 'SignUp',
});
return {
title: t('meta_title'),
description: t('meta_description'),
};
}
export default async function SignUpPage(props: ISignUpPageProps) {
const { locale } = await props.params;
setRequestLocale(locale);
return (
<SignUp path={getI18nPath('/sign-up', locale)} />
);
};
@@ -0,0 +1,59 @@
import { SignOutButton } from '@clerk/nextjs';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import Link from 'next/link';
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
import { BaseTemplate } from '@/templates/BaseTemplate';
export default async function DashboardLayout(props: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await props.params;
setRequestLocale(locale);
const t = await getTranslations({
locale,
namespace: 'DashboardLayout',
});
return (
<BaseTemplate
leftNav={(
<>
<li>
<Link
href="/dashboard/"
className="border-none text-gray-700 hover:text-gray-900"
>
{t('dashboard_link')}
</Link>
</li>
<li>
<Link
href="/dashboard/user-profile/"
className="border-none text-gray-700 hover:text-gray-900"
>
{t('user_profile_link')}
</Link>
</li>
</>
)}
rightNav={(
<>
<li>
<SignOutButton>
<button className="border-none text-gray-700 hover:text-gray-900" type="button">
{t('sign_out')}
</button>
</SignOutButton>
</li>
<li>
<LocaleSwitcher />
</li>
</>
)}
>
{props.children}
</BaseTemplate>
);
}
@@ -0,0 +1,25 @@
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { Hello } from '@/components/Hello';
export async function generateMetadata(props: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await props.params;
const t = await getTranslations({
locale,
namespace: 'Dashboard',
});
return {
title: t('meta_title'),
};
}
export default function Dashboard() {
return (
<div className="py-5 [&_p]:my-6">
<Hello />
</div>
);
}
@@ -0,0 +1,33 @@
import type { Metadata } from 'next';
import { UserProfile } from '@clerk/nextjs';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import { getI18nPath } from '@/utils/Helpers';
type IUserProfilePageProps = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata(props: IUserProfilePageProps): Promise<Metadata> {
const { locale } = await props.params;
const t = await getTranslations({
locale,
namespace: 'UserProfile',
});
return {
title: t('meta_title'),
};
}
export default async function UserProfilePage(props: IUserProfilePageProps) {
const { locale } = await props.params;
setRequestLocale(locale);
return (
<div className="my-6 -ml-16">
<UserProfile
path={getI18nPath('/dashboard/user-profile', locale)}
/>
</div>
);
};
+41
View File
@@ -0,0 +1,41 @@
import { ClerkProvider } from '@clerk/nextjs';
import { setRequestLocale } from 'next-intl/server';
import { routing } from '@/libs/I18nRouting';
import { ClerkLocalizations } from '@/utils/AppConfig';
export default async function AuthLayout(props: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await props.params;
setRequestLocale(locale);
const clerkLocale = ClerkLocalizations.supportedLocales[locale] ?? ClerkLocalizations.defaultLocale;
let signInUrl = '/sign-in';
let signUpUrl = '/sign-up';
let dashboardUrl = '/dashboard';
let afterSignOutUrl = '/';
if (locale !== routing.defaultLocale) {
signInUrl = `/${locale}${signInUrl}`;
signUpUrl = `/${locale}${signUpUrl}`;
dashboardUrl = `/${locale}${dashboardUrl}`;
afterSignOutUrl = `/${locale}${afterSignOutUrl}`;
}
return (
<ClerkProvider
appearance={{
cssLayerName: 'clerk', // Ensure Clerk is compatible with Tailwind CSS v4
}}
localization={clerkLocale}
signInUrl={signInUrl}
signUpUrl={signUpUrl}
signInFallbackRedirectUrl={dashboardUrl}
signUpFallbackRedirectUrl={dashboardUrl}
afterSignOutUrl={afterSignOutUrl}
>
{props.children}
</ClerkProvider>
);
}
@@ -0,0 +1,55 @@
import type { Metadata } from 'next';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import Image from 'next/image';
type IAboutProps = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata(props: IAboutProps): Promise<Metadata> {
const { locale } = await props.params;
const t = await getTranslations({
locale,
namespace: 'About',
});
return {
title: t('meta_title'),
description: t('meta_description'),
};
}
export default async function About(props: IAboutProps) {
const { locale } = await props.params;
setRequestLocale(locale);
const t = await getTranslations({
locale,
namespace: 'About',
});
return (
<>
<p>{t('about_paragraph')}</p>
<div className="mt-2 text-center text-sm">
{`${t('translation_powered_by')} `}
<a
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://l.crowdin.com/next-js"
>
Crowdin
</a>
</div>
<a href="https://l.crowdin.com/next-js">
<Image
className="mx-auto mt-2"
src="/assets/images/crowdin-dark.png"
alt="Crowdin Translation Management System"
width={128}
height={26}
/>
</a>
</>
);
};
@@ -0,0 +1,57 @@
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import Image from 'next/image';
import { CounterForm } from '@/components/CounterForm';
import { CurrentCount } from '@/components/CurrentCount';
export async function generateMetadata(props: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await props.params;
const t = await getTranslations({
locale,
namespace: 'Counter',
});
return {
title: t('meta_title'),
description: t('meta_description'),
};
}
export default function Counter() {
const t = useTranslations('Counter');
return (
<>
<CounterForm />
<div className="mt-3">
<CurrentCount />
</div>
<div className="mt-5 text-center text-sm">
{`${t('security_powered_by')} `}
<a
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://launch.arcjet.com/Q6eLbRE"
>
Arcjet
</a>
</div>
<a
href="https://launch.arcjet.com/Q6eLbRE"
>
<Image
className="mx-auto mt-2"
src="/assets/images/arcjet-light.svg"
alt="Arcjet"
width={128}
height={38}
/>
</a>
</>
);
};
+96
View File
@@ -0,0 +1,96 @@
import { getTranslations, setRequestLocale } from 'next-intl/server';
import Link from 'next/link';
import { DemoBanner } from '@/components/DemoBanner';
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
import { BaseTemplate } from '@/templates/BaseTemplate';
export default async function Layout(props: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await props.params;
setRequestLocale(locale);
const t = await getTranslations({
locale,
namespace: 'RootLayout',
});
return (
<>
<DemoBanner />
<BaseTemplate
leftNav={(
<>
<li>
<Link
href="/"
className="border-none text-gray-700 hover:text-gray-900"
>
{t('home_link')}
</Link>
</li>
<li>
<Link
href="/about/"
className="border-none text-gray-700 hover:text-gray-900"
>
{t('about_link')}
</Link>
</li>
<li>
<Link
href="/counter/"
className="border-none text-gray-700 hover:text-gray-900"
>
{t('counter_link')}
</Link>
</li>
<li>
<Link
href="/portfolio/"
className="border-none text-gray-700 hover:text-gray-900"
>
{t('portfolio_link')}
</Link>
</li>
<li>
<a
className="border-none text-gray-700 hover:text-gray-900"
href="https://github.com/ixartz/Next-js-Boilerplate"
>
GitHub
</a>
</li>
</>
)}
rightNav={(
<>
<li>
<Link
href="/sign-in/"
className="border-none text-gray-700 hover:text-gray-900"
>
{t('sign_in_link')}
</Link>
</li>
<li>
<Link
href="/sign-up/"
className="border-none text-gray-700 hover:text-gray-900"
>
{t('sign_up_link')}
</Link>
</li>
<li>
<LocaleSwitcher />
</li>
</>
)}
>
<div className="py-5 text-xl [&_p]:my-6">{props.children}</div>
</BaseTemplate>
</>
);
}
+134
View File
@@ -0,0 +1,134 @@
import type { Metadata } from 'next';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import { Sponsors } from '@/components/Sponsors';
type IIndexProps = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata(props: IIndexProps): Promise<Metadata> {
const { locale } = await props.params;
const t = await getTranslations({
locale,
namespace: 'Index',
});
return {
title: t('meta_title'),
description: t('meta_description'),
};
}
export default async function Index(props: IIndexProps) {
const { locale } = await props.params;
setRequestLocale(locale);
const t = await getTranslations({
locale,
namespace: 'Index',
});
return (
<>
<p>
{`Follow `}
<a
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://twitter.com/ixartz"
target="_blank"
rel="noreferrer noopener"
>
@Ixartz on Twitter
</a>
{` for updates and more information about the boilerplate.`}
</p>
<h2 className="mt-5 text-2xl font-bold">
Boilerplate Code for Your Next.js Project with Tailwind CSS
</h2>
<p className="text-base">
Next.js Boilerplate is a developer-friendly starter code for Next.js projects, built with Tailwind CSS and TypeScript.
{' '}
<span role="img" aria-label="zap">
</span>
{' '}
Designed with developer experience in mind, it includes:
</p>
<ul className="mt-3 text-base">
<li>🚀 Next.js with App Router support</li>
<li>🔥 TypeScript for type checking</li>
<li>💎 Tailwind CSS integration</li>
<li>
🔒 Authentication with
{' '}
<a
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://clerk.com?utm_source=github&amp;utm_medium=sponsorship&amp;utm_campaign=nextjs-boilerplate"
>
Clerk
</a>
{' '}
(includes passwordless, social, and multi-factor auth)
</li>
<li>📦 ORM with DrizzleORM (PostgreSQL, SQLite, MySQL support)</li>
<li>
💽 Dev database with PGlite and production with Neon (PostgreSQL)
</li>
<li>
🌐 Multi-language support (i18n) with next-intl and
{' '}
<a
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://l.crowdin.com/next-js"
>
Crowdin
</a>
</li>
<li>🔴 Form handling (React Hook Form) and validation (Zod)</li>
<li>📏 Linting and formatting (ESLint, Prettier)</li>
<li>🦊 Git hooks and commit linting (Husky, Commitlint)</li>
<li>🦺 Testing suite (Vitest, React Testing Library, Playwright)</li>
<li>🎉 Storybook for UI development</li>
<li>
🐰 AI-powered code reviews with
{' '}
<a
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://www.coderabbit.ai?utm_source=next_js_starter&utm_medium=github&utm_campaign=next_js_starter_oss_2025"
>
CodeRabbit
</a>
</li>
<li>
🚨 Error monitoring (
<a
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://sentry.io/for/nextjs/?utm_source=github&amp;utm_medium=paid-community&amp;utm_campaign=general-fy25q1-nextjs&amp;utm_content=github-banner-nextjsboilerplate-logo"
>
Sentry
</a>
) and logging (LogTape, an alternative to Pino.js)
</li>
<li>🖥 Monitoring as Code (Checkly)</li>
<li>
🔐 Security and bot protection (
<a
className="font-bold text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://launch.arcjet.com/Q6eLbRE"
>
Arcjet
</a>
)
</li>
<li>🤖 SEO optimization (metadata, JSON-LD, Open Graph tags)</li>
<li> Development tools (VSCode config, bundler analyzer, changelog generation)</li>
</ul>
<p className="text-base">
Our sponsors&apos; exceptional support has made this project possible.
Their services integrate seamlessly with the boilerplate, and we
recommend trying them out.
</p>
<h2 className="mt-5 text-2xl font-bold">{t('sponsors_title')}</h2>
<Sponsors />
</>
);
};
@@ -0,0 +1,72 @@
import type { Metadata } from 'next';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import Image from 'next/image';
import { routing } from '@/libs/I18nRouting';
type IPortfolioDetailProps = {
params: Promise<{ slug: string; locale: string }>;
};
export function generateStaticParams() {
return routing.locales
.map(locale =>
Array.from(Array.from({ length: 6 }).keys()).map(elt => ({
slug: `${elt}`,
locale,
})),
)
.flat(1);
}
export async function generateMetadata(props: IPortfolioDetailProps): Promise<Metadata> {
const { locale, slug } = await props.params;
const t = await getTranslations({
locale,
namespace: 'PortfolioSlug',
});
return {
title: t('meta_title', { slug }),
description: t('meta_description', { slug }),
};
}
export default async function PortfolioDetail(props: IPortfolioDetailProps) {
const { locale, slug } = await props.params;
setRequestLocale(locale);
const t = await getTranslations({
locale,
namespace: 'PortfolioSlug',
});
return (
<>
<h1 className="capitalize">{t('header', { slug })}</h1>
<p>{t('content')}</p>
<div className="mt-5 text-center text-sm">
{`${t('code_review_powered_by')} `}
<a
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://www.coderabbit.ai?utm_source=next_js_starter&utm_medium=github&utm_campaign=next_js_starter_oss_2025"
>
CodeRabbit
</a>
</div>
<a
href="https://www.coderabbit.ai?utm_source=next_js_starter&utm_medium=github&utm_campaign=next_js_starter_oss_2025"
>
<Image
className="mx-auto mt-2"
src="/assets/images/coderabbit-logo-light.svg"
alt="CodeRabbit"
width={128}
height={22}
/>
</a>
</>
);
};
export const dynamicParams = false;
@@ -0,0 +1,77 @@
import type { Metadata } from 'next';
import { getTranslations, setRequestLocale } from 'next-intl/server';
import Image from 'next/image';
import Link from 'next/link';
type IPortfolioProps = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata(props: IPortfolioProps): Promise<Metadata> {
const { locale } = await props.params;
const t = await getTranslations({
locale,
namespace: 'Portfolio',
});
return {
title: t('meta_title'),
description: t('meta_description'),
};
}
export default async function Portfolio(props: IPortfolioProps) {
const { locale } = await props.params;
setRequestLocale(locale);
const t = await getTranslations({
locale,
namespace: 'Portfolio',
});
return (
<>
<p>{t('presentation')}</p>
<div className="grid grid-cols-1 justify-items-start gap-3 md:grid-cols-2 xl:grid-cols-3">
{Array.from(Array.from({ length: 6 }).keys()).map(elt => (
<Link
className="hover:text-blue-700"
key={elt}
href={`/portfolio/${elt}`}
>
{t('portfolio_name', { name: elt })}
</Link>
))}
</div>
<div className="mt-5 text-center text-sm">
{`${t('error_reporting_powered_by')} `}
<a
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://sentry.io/for/nextjs/?utm_source=github&utm_medium=paid-community&utm_campaign=general-fy25q1-nextjs&utm_content=github-banner-nextjsboilerplate-logo"
>
Sentry
</a>
{` - ${t('coverage_powered_by')} `}
<a
className="text-blue-700 hover:border-b-2 hover:border-blue-700"
href="https://about.codecov.io/codecov-free-trial/?utm_source=github&utm_medium=paid-community&utm_campaign=general-fy25q1-nextjs&utm_content=github-banner-nextjsboilerplate-logo"
>
Codecov
</a>
</div>
<a
href="https://sentry.io/for/nextjs/?utm_source=github&utm_medium=paid-community&utm_campaign=general-fy25q1-nextjs&utm_content=github-banner-nextjsboilerplate-logo"
>
<Image
className="mx-auto mt-2"
src="/assets/images/sentry-dark.png"
alt="Sentry"
width={128}
height={38}
/>
</a>
</>
);
};
+36
View File
@@ -0,0 +1,36 @@
import { sql } from 'drizzle-orm';
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
import * as z from 'zod';
import { db } from '@/libs/DB';
import { logger } from '@/libs/Logger';
import { counterSchema } from '@/models/Schema';
import { CounterValidation } from '@/validations/CounterValidation';
export const PUT = async (request: Request) => {
const json = await request.json();
const parse = CounterValidation.safeParse(json);
if (!parse.success) {
return NextResponse.json(z.treeifyError(parse.error), { status: 422 });
}
// `x-e2e-random-id` is used for end-to-end testing to make isolated requests
// The default value is 0 when there is no `x-e2e-random-id` header
const id = Number((await headers()).get('x-e2e-random-id')) || 0;
const count = await db
.insert(counterSchema)
.values({ id, count: parse.data.increment })
.onConflictDoUpdate({
target: counterSchema.id,
set: { count: sql`${counterSchema.count} + ${parse.data.increment}` },
})
.returning();
logger.info('Counter has been incremented');
return NextResponse.json({
count: count[0]?.count,
});
};
+64
View File
@@ -0,0 +1,64 @@
import type { Metadata } from 'next';
import { hasLocale, NextIntlClientProvider } from 'next-intl';
import { setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { PostHogProvider } from '@/components/analytics/PostHogProvider';
import { DemoBadge } from '@/components/DemoBadge';
import { routing } from '@/libs/I18nRouting';
import '@/styles/global.css';
export const metadata: Metadata = {
icons: [
{
rel: 'apple-touch-icon',
url: '/apple-touch-icon.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
url: '/favicon-32x32.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
url: '/favicon-16x16.png',
},
{
rel: 'icon',
url: '/favicon.ico',
},
],
};
export function generateStaticParams() {
return routing.locales.map(locale => ({ locale }));
}
export default async function RootLayout(props: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await props.params;
if (!hasLocale(routing.locales, locale)) {
notFound();
}
setRequestLocale(locale);
return (
<html lang={locale}>
<body>
<NextIntlClientProvider>
<PostHogProvider>
{props.children}
</PostHogProvider>
<DemoBadge />
</NextIntlClientProvider>
</body>
</html>
);
}
+26
View File
@@ -0,0 +1,26 @@
'use client';
import * as Sentry from '@sentry/nextjs';
import NextError from 'next/error';
import { useEffect } from 'react';
import { routing } from '@/libs/I18nRouting';
export default function GlobalError(props: {
error: Error & { digest?: string };
}) {
useEffect(() => {
Sentry.captureException(props.error);
}, [props.error]);
return (
<html lang={routing.defaultLocale}>
<body>
{/* `NextError` is the default Next.js error page component. Its type
definition requires a `statusCode` prop. However, since the App Router
does not expose status codes for errors, we simply pass 0 to render a
generic error message. */}
<NextError statusCode={0} />
</body>
</html>
);
}
+13
View File
@@ -0,0 +1,13 @@
import type { MetadataRoute } from 'next';
import { getBaseUrl } from '@/utils/Helpers';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: '/dashboard',
},
sitemap: `${getBaseUrl()}/sitemap.xml`,
};
}
+14
View File
@@ -0,0 +1,14 @@
import type { MetadataRoute } from 'next';
import { getBaseUrl } from '@/utils/Helpers';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: `${getBaseUrl()}/`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 0.7,
},
// Add more URLs here
];
}