Skip to content

ljosberinn/next-karma-docs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 

Repository files navigation

  • Getting Started

    • Setting up a new project

      • .env

        An overview over next-karmas use of environment variables.

        next-karma makes extensive use of environment variables. A short overview before we dive into specific implementations:

        i18n

        Required environment variables for internationalization.

        • NEXT_PUBLIC_ENABLED_LANGUAGES

          A list of the currently enabled languages.

          • type: string
          • example: en,de
        • NEXT_PUBLIC_FALLBACK_LANGUAGE

          Should a user with an unknown Accept-Language header visit the site, this language will be used as fallback.

          Must be a part of NEXT_PUBLIC_ENABLED_LANGUAGES.

          • type: string
          • default: en

        auth

        Required environment variables for authentication.

        • NEXT_PUBLIC_ENABLED_PROVIDER

          A list of the currently enabled 3rd party provider.

          Each provider must also have both CLIENT_ID and CLIENT_SECRET defined.

          • type: string
          • example: google,discord
        • NEXT_PUBLIC_SESSION_LIFETIME

          Session lifetime of the httpOnly cookie in seconds.

          • type: number
          • default: 28800 (8 hours)
        • GITHUB_CLIENT_ID

        • GITHUB_CLIENT_SECRET

          GitHub App client ID and secret. See Authentication > GitHub.

        • GOOGLE_CLIENT_ID

        • GOOGLE_CLIENT_SECRET

          Google App client ID and secret. See Authentication > Google.

        • FACEBOOK_CLIENT_ID

        • FACEBOOK_CLIENT_SECRET

          Facebook App client ID and secret. See Authentication > Facebook.

        • DISCORD_CLIENT_ID

        • DISCORD_CLIENT_SECRET

          Discord App client ID and secret. See Authentication > Discord.

        sentry

        Environment variables for Sentry. These will be automatically created by the Vercel Integration.

        • NEXT_PUBLIC_SENTRY_DSN

          Sentry API endpoint.

        • SENTRY_ORG

          The organization this project belongs to.

        • SENTRY_PROJECT

          The name of the project within Sentry.

        • SENTRY_AUTH_TOKEN

          Token created by Sentry to support sourcemap uploads.

        GitHub Actions

        How to set up CI/CD with GitHub

        To deploy via GitHub Actions you need the following secrets set in your repository:

        • CC_TEST_REPORTER_ID
          1. Login to CodeClimate
          2. After setting up your repository, go to Repo Settings
          3. Select Test coverage in the left menu
          4. Copy the TEST REPORTER ID
        • VERCEL_TOKEN
        • VERCEL_PROJECT_ID
        • VERCEL_ORG_ID
        • GITHUB_TOKEN
          • create one here with repository scope

        To add a secret, go to your repositories Settings tab and then select Secrets.

        VERCEL_PROJECT_ID and VERCEL_ORG_ID can be retrieved from your projects .vercel/project.json file. For that, you will need to globally install the Vercel CLI:

        npm i -g vercel

        After logging in, in your project simply execute vercel, it will create the required file (more info here).

        If you wish to skip any of those steps, edit .github/workflows/deploy.yml accordingly.

      • GitHub Issue Template

        next-karma comes with two templates out of the box:

        • Bug Report

          Associated label(s): bug

        • Feature Request

          Associated label(s): enhancement

        You may edit them in .github/ISSUE_TEMPLATE/Bug Fix.md.

      • GitHub PR Template

        next-karma comes with one template out of the box:

        • Bug Fix

          Associated label(s): bug

        You may edit it in .github/PULL_REQUEST_TEMPLATE/Bug Fix.md.

      • Using Sentry

        How to set up Sentry

        Requirements

        Setup

        next-karma leverages several of Sentrys tools:

        • preconfigured, global ErrorBoundary in _app.tsx via @sentry/react

          This global ErrorBoundary is just here for the worst case. You should use some yourself additionally.

          Due to Sentry being initialized in the UI, any frontend error will be caught, traced and reported.

        • next-connect API route middleware via @sentry/node

          When using the included sentryMiddleware, API route errors will be caught, traced and reported.

        • release management with source map support via @sentry/webpack-plugin

          During build, a separate release visible within the Sentry dashboard will be created.

    • Authentication

      • Custom Authentication

      • Discord

        How to setup OAuth2 with Discord

        Requirements

        • Discord Account

        Steps

        1. go to https://discord.com/developers/applications
        2. create a new application
        3. choose a name
        4. copy CLIENT ID and CLIENT SECRET into .env
        5. select the OAuth2 tab on the left
        6. enter http://localhost:3000/api/v1/auth/discord as well as any staging/production URLs you might need

        If you need more profile access than just the basics, read up on how to here.

      • Facebook

        How to setup OAuth2 with Facebook

        Requirements

        • Facebook Account

        Steps

        1. go to https://developers.facebook.com/apps/
        2. add a new app
        3. choose a name
        4. add Facebook Login from the list of products
        5. choose Web
        6. enter your site url (you may change this later too)
        7. in the left navigation, go to Settings > Basic
        8. copy App ID and App Secret fields over to the .env file
        9. in the left navigation, go to Facebook Login > Settings
        10. add any staging/production URLs to the field Valid OAuth Redirect URIs
      • GitHub

        How to setup OAuth2 with GitHub

        Requirements

        • GitHub Account

        Steps

        1. go to https://github.com/settings/developers
        2. create a New OAuth App
        3. copy Client ID and Client Secret over into .env
        4. set Authorization callback URL according to your current environment

        GitHub does not allow multiple redirect URLs. For each environment, you will need a separate app.

      • Google

        How to setup OAuth2 with Google

        Requirements

        • Google Account

        Steps

        1. go to https://console.developers.google.com/apis/credentials
        2. click on CREATE CREDENTIALS
        3. select OAuth client ID
        4. select Web Application as Application type
    • Internationalization

      An overview over the react-i18next integration within next-karma.

      react-i18next

      i18next is a well established, well maintained internationalization framework. next-karma initializes with defaults which you may find in karma/client/i18n.ts.

      Shoutout to UnlyEd & isaachinman

      At the time of writing, the commonly used i18n solution for Next.js, next-i18next is serverless compatible, but still relies on App.getInitialProps which is no longer recommended when using Next.js 9.3+. UnlyEd and their project next-right-now implemented a solution which next-karma draws inspiration from.

      If you are already familiar with next-i18next, next-karmas solution will feel similar in how to set up.

      Setup

      Just like next-i18next, next-karma expects your internationalization data to be stored in public/static/locales/$SLUG/$NAMESPACE.json files.

      For demonstration purposes, next-karma ships with 4 granular namespaces: auth, i18n, serviceWorker and theme in both English and German.

      Namespaces need to be imported defined within karma/server/i18n/cache.ts following the existing pattern.

      Finally, on a per page basis, follow this pattern:

      // e.g. /pages/page.tsx
      import type { WithKarma } from '../karma/client/Karma';
      import { KarmaProvider, createGetServerSideProps } from '../karma/client/Karma';
      import type { Namespace } from '../karma/server/i18n/cache';
      
      // adjust accordingly
      export type PageProps = WithKarma;
      
      // eslint-disable-next-line import/no-default-export
      export default function Page({ karma }: PageProps): JSX.Element {
        return (
          <KarmaProvider {...karma}>
            <h1>next-karma</h1>
          </KarmaProvider>
        );
      }
      
      const i18nNamespaces: Namespace[] = ['serviceWorker', 'theme'];
      
      export const getServerSideProps = createGetServerSideProps({ i18nNamespaces });

      This ensures only the namespaces you need for this specific site will be loaded in getServerSideProps while keeping type-safety.

      Language detection

      next-karma will automatically detect the language server-side based on (ordered by priority):

      • cookies (react-i18next sets a cookie to remember the language)
      • HTTP Accept-Language header through an internal parser to avoid unnecessary dependencies
      • dev-defined FALLBACK_LANGUAGE (see .env chapter)

      Testing

      Following the spirit of @testing-librarys integrative testing approach, all components have access to all languages and namsepaces. This means you can freely change keys in your translation. However, you might have to adjust tests when changing your actual translation as this is a change which affects users.

      Additional Features

      Adjusting attributes

      When a user changes the language, next-karma will adjust the

    • Included Components

      next-karma comes with a few predefined components for you to use. None of them are mandatory. All are tested.

      • Core Components

        These are components that next-karma uses internally. It is recommended to keep them where they are.

        Location: /karma/client/components

        Used at: /karma/client/Karma.tsx

        • <MetaThemeColorSynchronizer />

          As the name already hints - synchronizes the <meta name="theme-color" /> tag to the currently active color mode.

          Dependencies:

          • @chakra-ui

          When to touch:

          • when you change the default background colors

          More info:

        • <ServiceWorker />

          Again, as the name already hints - registers the service worker provided by next-offline.

          Disabled in development! If you run yarn build in development, the service-worker.js in public will however be registered as the file is now present. Keep an eye out for accidental registrations in dev. This is intentional, as you can test the component in dev this way.

          Dependencies:

          • @chakra-ui
          • i18next
          • react-i18next

          When to touch:

          • when you want to add functionality
          • when you want to change the internationalization namespace of the service worker (default: serviceWorker)
          • when you don't care about service workers, you can safely delete all references of the component:
            • delete usage of component in src/client/Karma.tsx
            • delete src/client/__tests__/ServiceWorker.test.tsx
            • uninstall next-offline
            • remove next-offline config in next.config.js
      • Other

        These are demo components next-karma partially uses in the docs. They are also a showcase of @chakra-ui capabilities. Do with them what you want.

        Location: /src/client/components

        • <ColorModeSwitch />

          One of two color mode toggles coming with next-karma. Changes the color mode on click. Fully accessible.

          Dependencies:

          • @chakra-ui
          • react-i18next
          • react-icons
        • <ColorModeSwitchAlt />

          One of two color mode toggles coming with next-karma. Changes the color mode on click. Fully accessible.

          Dependencies:

          • @chakra-ui
          • i18next
          • react-i18next
          • react-icons
        • <CustomPWAInstallPrompt />

          Very barebones example component on how to integrate a custom PWA install prompt.

          Relies on the beforeinstallprompt event which is only supported by Chrome and Android WebView!

          Dependencies:

          • @chakra-ui
        • <ExternalLink />

          Preconfigured component with sane defaults for linking to external sites, built upon @chakra-ui/Link.

          Dependencies:

          • @chakra-ui
        • <InternalLink />

          Preconfigured component for linking internally, built upon @chakra-ui/Link and next/link.

          Dependencies:

          • @chakra-ui
        • <LanguageSwitch />

          Menu-based language selection with bundle-loading support.

          Dependencies:

          • @chakra-ui
          • i18next
          • react-i18next
          • react-icons
          • react-flag-kit
        • <WebShareButton />

          Mobile share button using the navigator.share API. Only renders where navigator.share is supported.

          Dependencies:

          • @chakra-ui
          • react-icons
  • Guides

    • API routes with next-connect

      • Using custom middlewares

        authNSecurity

        A common requirement for API routes is to protect them from unauthorized access. The authNSecurityMiddleware ensures that only authenticated requests make it through your actual API route and on top decrypts the stored session, exposing it on the request object.

        export default nextConnect().use(authNSecurityMiddleware).get(myApiRoute);

        To be able to access the manipulated request property in TypeScript, you will need to extend the RequestHandler interface of next-connect. An example can be found in the /pages/api/v1/user/me.ts route.

        const SESSION_COOKIE_NAME = "session";
        
        interface AuthenticatedRequest {
          [SESSION_COOKIE_NAME]: User;
        }
        
        const meHandler: RequestHandler<AuthenticatedRequest> = (req, res) => {
          return res.json(req[SESSION_COOKIE_NAME]);
        };

        expectJSONBody

        Many requests to the backend have a JSON body which has to be parsed before being able to use it. The expectJSONBodyMiddleware ensures that given a req.body, it contains a valid JSON, parses it and overwrites the original req.body. if not, it bails with status code 400 (BAD REQUEST).

        For further implementation detail, please inspect /karma/server/middlewares/expectJSONBody.ts.

        export default nextConnect()
          .use(expectJSONBodyMiddleware)
          .post(myRouteExpectingJSON);

        The request object supports this out of the box, so this is natively type-safe.

        sentryMiddleware

        Just like on the frontend, we should know once something goes wrong in the backend. The sentryMiddleware will boot Sentry in an API route, attach metadata such as url, method, host, cookies, query, headers and body.

        export default nextConnect()
          .use(sentryMiddleware)
          .post(myRouteThatMightError);
    • Authentication

      • Adding another provider
    • Internationalization

      • Adding a language
      • Adding a namespace
  • Testing

    • Testing Components

      @testing-library/react

      next-karma utilizes jest in combination with @testing-library/react for all component tests.

      If you are unfamiliar with @testing-library, I'd recommend to familiarize yourself with it first. The basics are easy to pick up.

      It is highly recommended to use Testing Playground when writing tests.

      Custom Render

      next-karma comes with a predefined custom render function for component tests. Its API extends the regular @testing-library render function:

      render(<Component />, options);

      It ensures that all tested components are rendered within the following providers:

      • ChakraProvider with default theme
      • I18NextProvider with a static i18next instance with access to every translation
      • AuthContextProvider which defaults to no session

      Testing components receiving react-i18next props

      Some components may receive properties of the result of useTranslation via props. To prevent mocking those all the time and having to resort to check for the existence of translation keys, you may use this:

      render(<Component />, {
        i18n: {
          // may also be an array of strings; inherits from useTranslation types
          namespace: "namespace-to-use",
        },
      });

      To rename props you may pass an additional alias object. This will overwrite provided i18n props with the actual props while preserving type-safety.

      render(
        <Component
          translate={jest.fn()}
          i18nInstance={jest.fn()}
          i18nReady={false}
        />,
        {
          i18n: {
            namespace: "namespace-to-use",
            alias: {
              t: "translate",
              i18n: "i18nInstance",
              ready: "i18nReady",
            },
          },
        }
      );

      Testing with a session

      Some components may depend on an existing session. To allow those components to properly render in tests, you may pass a session to render. It defaults to null and if given, must match the implementation of AuthContextDefinition['user'] from the AuthContextProvider.

      render(<Component />, {
        session: mockGithubProfile,
      });

      Accessibility testing

      To easen accessibility testing, a wrapper around jest-axe is included. From the same file where the custom render is defined, simply import testA11Y:

      it("passes a11y test", async () => {
        await testA11Y(<MyComponent />, options);
      });

      If you need to perform some kind of action, e.g. opening a Menu or Modal before testing for accessibility, you may also pass an element to testA11Y:

      it("passes a11y test when opened", async () => {
        const { container } = render(<MyComponent />);
      
        const button = screen.getByRole("button");
      
        // actually open the menu as other elements are not rendered before
        userEvent.click(button);
      
        await testA11Y(container);
      });

      To disable certain html validation rules, e.g. when unavoidable for reasons beyond your control, you may pass an object axeOptions to options, which is a proxy for axe-core configuration options.

      it("passes a11y test", async () => {
        await testA11Y(<MyComponent />, {
          axeOptions: {
            rules: {
              "image-alt": { enabled: false },
            },
          },
        });
      });

      HTML Validity testing

      To easen HTML validity testing, a wrapper around html-validate is included. From the same file where the custom render is defined, simply import validateHtml:

      it("contains valid html", () => {
        validateHtml(<MyComponent />, options);
      });

      If you need to perform some kind of action, e.g. opening a Menu or Modal before testing for accessibility, you may also pass an element to validateHtml:

      it("passes a11y test when opened", () => {
        const { container } = render(<MyComponent />);
      
        const button = screen.getByRole("button");
      
        // actually open the menu as other elements are not rendered before
        userEvent.click(button);
      
        validateHtml(container);
      });

      To disable certain html validation rules, e.g. when unavoidable for reasons beyond your control, you may pass an object htmlValidate to options, which is a proxy for html-validate options:

      it("contains valid html", () => {
        validateHtml(<MyComponent />, {
          htmlValidate: {
            rules: {
              "attribute-boolean-style": "off",
            },
          },
        });
      });
  • Testing API Routes

  • Best Practices

    TypeScript

    • only type non-primitive types to let inferrence work
    • use the included WithChildren interface to explicitly type children over the use of React.FC
    • name component props not just Props, but export them and name them like the component, e.g. Button has ButtonProps

    API Routes

    • when using Sentry and next-connect, always use sentryMiddleware as first middleware

    Testing

    • avoid naming tests with conjunctives:
      • do: changes theme on click
      • don't: should change theme on click
    • with required props, always import the type/interface of the components props and use it for your default props definition
    • always use the custom render function unless no render context needed
    • always use the accessibility testA11Y function
      • always name the a11y test passes a11y test | ...given default props | ...when opened
    • always use the html validation validateHtml function
      • always name the html test contains valid html | ...given default props | ...when opened

About

Documentation for next-karma

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published