Configuration Files
This project comes with a set of configuration files that are used to configure things such as onboarding, emails, etc.
Here’s a breakdown of each file and what it’s used for:
app.config.ts
You can refer to the TestimonialSidePanel.tsx
, Onboarding.tsx
, FeedbackWidget.tsx
(used for the reportBug and shareFeedback booleans below) and FormStatusMessage.tsx
components to see what the files inside the app look like.
testimonialSidePanel: {
isEnabled: true, // shows or hides the testimonials side panel
direction: "right", // the direction of the testimonials side panel
},
onboarding: {
isEnabled: true, // shows or hides the onboarding
},
feedback: {
widgets: {
reportBug: {
isEnabled: true, // shows or hides the report bug widget
formUrl: [insert-form-url],
},
shareFeedback: {
isEnabled: true, // shows or hides the share feedback widget
formUrl: [insert-form-url],
},
},
forms: {
accountDeletion: {
isEnabled: true, // shows or hides the account deletion form
formUrl: [insert-form-url],
},
},
},
Your To-Do’s:
Feel free to configure the following features as desired as these are all optional. You can refer to their components to see what they look like inside the app.
- testimonialSidePanel (TestimonialSidePanel.tsx)
- onboarding (OnboardingFlow.tsx)
- reportBug (FeedbackWidget.tsx)
- shareFeedback (FeedbackWidget.tsx)
- accountDeletionForm (FormStatusMessage.tsx)
Each feature can be toggled in app.config.ts
using their respective isEnabled
flags.
TL;DR: no action required. You can just leave the default settings.
pricing.config.ts
This file is used to configure the pricing of the app and contains all the Stripe related information such as the pricing plans, free trial, etc.
You’re gonna notice that we have a pricingCardFeatures
and defaultPricingFeatures
object.
pricingCardFeatures
is used to configure what features to show on the pricing card (PricingCard.tsx)defaultPricingFeatures
is used to configure what features to show on the pricing comparision table (PricingComparison.tsx)
export const defaultPricingPlans: {
monthly: DefaultPricingPlan[];
annual: DefaultPricingPlan[];
oneTime: DefaultPricingPlan[];
} = {
monthly: [
...
{
name: SubscriptionTier.FOUNDERS,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
price: "$99",
billing_period: BillingPeriod.MONTHLY,
billing_plan: BillingPlan.RECURRING,
is_highlighted: false,
stripe_price_id: "price_1QVHMLK9e7MkYDNk1hygYAxm",
subscription_tier: SubscriptionTier.FOUNDERS,
},
],
annual: [
...
{
name: SubscriptionTier.FOUNDERS,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
price: "$990",
billing_period: BillingPeriod.YEARLY,
billing_plan: BillingPlan.RECURRING,
is_highlighted: false,
stripe_price_id: "price_1QVHMjK9e7MkYDNklyysiqq5",
subscription_tier: SubscriptionTier.FOUNDERS,
},
],
oneTime: [
{
name: SubscriptionTier.ESSENTIALS,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
price: "$999",
billing_period: BillingPeriod.LIFETIME,
billing_plan: BillingPlan.ONE_TIME,
is_highlighted: false,
stripe_price_id: "price_1QZDDyK9e7MkYDNkC91qiQl4",
subscription_tier: SubscriptionTier.ESSENTIALS,
},
...
],
};
Your To-Do’s:
- You’ll have to configure the pricingCardFeatures and defaultPricingFeatures objects to contain the features of your app, e.g. 1 GB File Storage, 5 GB Bandwidth, etc.
- You’ll need to configure the name, description, price, stripe_price_id and subscription_tier for each of the plans. You can find the stripe_price_id in Stripe.
FYI: You’ll be shown in the Stripe setup [here] how to create the plans and get the stripe_price_id for each plan.
Do NOT remove the price_free
object from the defaultPricingPlans
object nor change the stripe_price_id
of the free plan. These are used for our free plan and used throughout the codebase.
billing.config.ts
isFreePlanEnabled: true, // this will be overridden when ONE_TIME is enabled
billingPlan: BillingPlan.ONE_TIME, // choose between ONE_TIME or RECURRING
freeTrial: {
isEnabled: true, // shows or hides the free trial
duration: 14, // the duration of the free trial
},
If OTP is enabled, the free plan as well as the free trial will be disabled as it’s not usual to have a free plan / free trial with a OTP.
Your To-Do’s:
- Set
billingPlan
to eitherBillingPlan.ONE_TIME
(for OTPs) orBillingPlan.RECURRING
(for recurring subscriptions). - If recurring subscriptions are enabled, set other values such as the
freeTrial
duration andisFreePlanEnabled
, etc.
auth.status.config.ts
This boilerplate comes with a set of pre-built success and error messages out of the box. You can refer to the StatusPage.tsx
page to see what the files inside the app look like.
It’s also possible to add or remove success and error messages. It’s as simple as adding a new object inside the appropiate success or error object.
The KEY of each object such as token-expired
is the name of the error type. E.g. in order to show that specific error page, redirect the user to the /auth-status?mode=token-expired
route.
export const ERROR_TYPES: { [K in ErrorType]: StatusConfig } = {
"token-expired": {
badge: "LINK ERROR",
title: "Link",
highlight: "Has Expired",
description:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Vitae expedita obcaecati modi, nisi reiciendis mollitia!",
primaryButton: {
href: "/auth/sign-in?method=magic-link",
label: "Request New Link",
},
},
...
};
export const SUCCESS_TYPES: { [K in SuccessType]: StatusConfig } = {
"email-confirmed": {
badge: "EMAIL CONFIRMED",
title: "Email",
highlight: "Confirmed",
description: `Your email has been confirmed. You can now continue using ${TextConstants.TEXT__COMPANY_TITLE}.`,
primaryButton: {
href: "/choose-pricing-plan",
label: "Continue",
},
},
...
};
Your To-Do’s:
- Feel free to add or remove error and success messages as desired. These are all optional. However, do not change the KEYS of the existing messages as these are used throughout the code.
TL;DR: no action required. You can just leave the default settings.
email.config.ts
This boilerplate comes with a set of pre-built email templates out of the box - built with Loops.
For emails to work, please connect this boilerplate with Loops. You can refer to the Emails section [here] on how to do this.
You can also add / remove emails from the email.config.ts
file. In order to add a new email, please refer to the explainer video below.
Your To-Do’s:
- Feel free to add or remove emails. These are all optional. However, if an email is removed, make sure to also remove it throughout the codebase where it’s being used.
TL;DR: no action required. You can just leave the default settings.
routes.config.ts
This file is used to configure the routes of the app. It’s divided into 3 main sections:
- PUBLIC: These are the routes that are ALWAYS accessible - even when authenticated.
- AUTH: These are the routes that are used for authentication flows such as login, signup, forgot password, etc. These can’t be accessed when authenticated.
- PROTECTED: These are the routes that are protected and require the user to be authenticated.
Feel free to just add or remove routes from the appropiate section as the middleware.ts
will handle the rest.
Your To-Do’s:
- Feel free to add or remove routes. These are all optional. However, if a route is removed, make sure to also remove it throughout the codebase where it’s being used.
TL;DR: no action required. You can just leave the default settings.
export const ROUTES_CONFIG = {
PUBLIC: {
LANDING_PAGE: "/",
STATUS_ERROR: "/auth-status/error",
STATUS_SUCCESS: "/auth-status/success",
},
AUTH: {
SIGN_IN: "/auth/sign-in",
SIGN_UP: "/auth/sign-up",
/**
* the following routes are (password-management) routes
* these are used for password management flows such as forgot password and update password
*/
FORGOT_PASSWORD: "/auth/forgot-password",
UPDATE_PASSWORD: "/auth/update-password",
/**
* the following routes are (handlers) routes
* these are used for handling authentication flows such as email and google login
*/
CONFIRM: "/auth/confirm",
CALLBACK: "/auth/callback",
},
PROTECTED: {
USER_DASHBOARD: "/app",
USER_BILLING: "/app/billing",
USER_PROFILE: "/app/user-profile",
/**
* the following routes are (standalone) routes
* these do not contain a layout unlike the other protected routes
*/
CHOOSE_PRICING_PLAN: "/choose-pricing-plan",
ONBOARDING: "/onboarding",
PLAN_CONFIRMATION: "/plan-confirmation",
},
} as const;
seo.config.ts
This file is used to configure the SEO of the app. It’s used to set the title, description, etc.
Before launching an app with this boilerplate, please make sure to replace all the boilerplate data such as title, description, etc. with the actual data! 😁
export const seoConfig: SEOConfig = {
default: {
title: "Lightweight SaaS Boilerplate • 2mrw",
template: "%s • 2mrw", // used for page-specific titles: "Sign In • 2mrw"
description:
"Launch your SaaS faster with our Next.js and Supabase boilerplate.",
keywords: ["SaaS boilerplate", "Next.js template", "Supabase starter"],
url: process.env.NEXT_PUBLIC_SITE_URL ?? "https://2mrw.dev",
locale: "en-US",
// open graph image (used by Facebook, LinkedIn, etc.)
ogImage: {
url: "/og-image.jpg",
width: 1200,
height: 630,
alt: "2mrw SaaS Boilerplate",
},
// twitter config (used by Twitter)
twitter: {
handle: "@timohuennebeck",
site: "@2mrw",
cardType: CardType.SUMMARY_LARGE_IMAGE,
},
},
...
};
Your To-Do’s:
- Please update the
seoConfig
with the actual data for your app such as the title, description, keywords, etc.
Generating SEO For Specific Pages
You can also generate SEO for specific pages. This is useful if you want to have a different title, description, etc. for a specific page.
const seo = generateSeo(seoConfig, {
title: "Sign In",
description: "You can sign in to your account using email or google.",
keywords: ["Sign in", "Email", "Google"],
url: "/auth/sign-in",
});
This will generate the following SEO object behind the scenes:
{
title: "Sign In • 2mrw",
description: "You can sign in to your account using email or google.",
keywords: ["Sign in", "Email", "Google"],
url: "https://2mrw.dev/auth/sign-in",
}