ConfigurationConfiguration Files

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 either BillingPlan.ONE_TIME (for OTPs) or BillingPlan.RECURRING (for recurring subscriptions).
  • If recurring subscriptions are enabled, set other values such as the freeTrial duration and isFreePlanEnabled, 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",
}