*Note: For project-specific documentation, please check your project's README.md
.*
Table of contents
- Getting started
- Commands
- Environment variables
- Directory structure
- Pages
- Components
- Testing
- Styling
- Localisation
- Authentication
- Multi-tenancy
- API code generation
- SVG generation
- Included libraries
- Default headers
- Content Security Policy
- Browser compatibility
- Error monitoring
- Dynamic
robots.txt
- Health check
Run the following command to scaffold a project:
npx create next-app --example=https://github.com/lightbasenl/scaffold
Next, run through every occurrence of TODO(platform)
and follow the instructions.
The following commands are included:
Command | Description |
---|---|
yarn dev |
Start development server |
yarn build |
Make production build |
yarn start |
Run production build |
yarn format |
Format and lint code w/ Prettier & ESLint |
yarn generate |
Alias for generate:api |
yarn generate:api |
Generate API code |
yarn generate:svg |
Generate SVG components |
yarn test:e2e |
Run end-to-end tests |
Command | Description |
---|---|
SENTRY_AUTH_TOKEN |
This is used to push the source-maps to Sentry, and create a new release |
SENTRY_RELEASE |
This should be set to the commit SHA hash, so it can be tracked which deploy introduced regressions |
NEXT_PUBLIC_SENTRY_DSN |
This is the Sentry DSN that should be used to log errors to |
...
├── ...
├── assets // .svg files for generating SVG components
├── config // Project configuration files
│ └── tenants.json // Tenant configuration
├── e2e // End-to-end test files
├── public // Publicly hosted assets (i.e. mail assets)
├── scripts // Project scripts
├── src
│ ├── assets
│ ├── auth // Authentication helpers
│ ├── components
│ ├── css
│ ├── generated // Generated API code (hooks, api client)
│ ├── hooks // Project hooks
│ ├── lib // Helper functions
│ ├── locales // Localisation files
│ ├── pages
│ └── tenants // Multi-tenancy helpers
├── ...
Pages use the .page.tsx
extension, API routes and middleware use .api.ts
.
Note: These page extensions also apply to _app
, _document
and middleware
files.
Using page extensions allows us to keep certain components close to the pages they're used on.
Components that are used only on a few pages and/or are very specific to those pages, should be placed in a
/component
directory near their page.
For example:
...
├── ...
├── src
│ ├── pages
│ │ ├── about-us
│ │ │ ├── components
│ │ │ │ └── Employee.tsx 👈 The component
│ │ │ └── index.page.tsx 👈 The page component
├── ...
Components that can be used throughout the app are called base components. These live in the /src/components
directory.
Some examples of base components are: <Input />
, <Checkbox />
, <TextArea />
, <Button />
, etc.
End-to-end tests are set up with Playwright and located in /e2e
.
On CI, a clean backend instance is spun up to make sure the environment is always the same.
Unit tests use Jest and are located with the code they test. For example: if you have a
function named ucfirst
located in /src/lib/ucfirst.ts
, the unit test should be located in
/src/lib/ucfirst.test.ts
.
TailwindCSS is used for styling. It allows for quick prototyping and developing new functionality without having to think about naming classes and managing (S)CSS files.
next-i18next is set up out of the box.
Localisation files are located in src/locales
. Every file within this folder is a namespace, that can be
loaded in a page by passing the namespaces parameter to defaultServerSideProps
or getPageProps
. By
default, the public
and private
namespaces are included.
Authentication with Compas-powered backends typically happens with an Authorization
header.
The authAxiosRequestInterceptor
and authAxiosResponseErrorInterceptor
interceptors handle setting the
headers and manage authentication cookies.
For server-side authorization, an authDescription
can be passed to defaultServerSideProps
.
export const getServerSideProps = defaultServerSideProps({
authDescription: {
enforceSessionType: "user",
enforceLoginType: "anonymousBased",
},
namespaces: ["private", "public"],
});
Client-side authorization can be done through the use of the useAuthenticate
hook. Note however, that
client-side authorization may cause Cumulative Layout Shift in your application.
Multi-tenancy is a feature that allows using the same application instance for multiple clients or oganisations.
The tenant configuration is located in config/tenants.json
. This file contains possible configurations for
hostnames that serve the application.
This file is typically managed by @lpc/sync
and should not be changed manually.
Tenant pages are located in src/pages/_tenants/[tenants]
and must always have a getServerSideProps
export
with defaultServerSideProps
or a getStaticProps
export with getPageProps
.
Tenant details can be read from the TenantConfigContext
context with the useContext
hook or from
pageProps
in _app.page.tsx
.
By default, the tenant details are used to create an API client in _app.page.tsx
.
Some extra steps need to be taken in order to generate tenant-aware static pages.
A static page needs to call getPageProps
inside getStaticProps
and return paths returned by
buildStaticPaths
in getStaticPaths
.
E.g.
export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (typeof ctx.params?.tenant !== "string") {
throw new Error("Tenant is required!");
}
return {
props: {
...(await getPageProps({ tenant: ctx.params?.tenant, locale: ctx.locale })),
},
};
};
export async function getStaticPaths() {
const pageTree = buildStaticPaths([{}]);
return {
paths: pageTree,
fallback: "blocking",
};
}
The tenant error page can be customized in 412.page.tsx
.
You can use the TENANT_ORIGIN
environment variable to make the tenant resolve to any of the tenant’s
specified in the tenant configuration.
# .env.local
TENANT_ORIGIN=scaffold.dev.lightbase.nl
You can use the TENANT_API_URL
environment variable to specify a local API url to use for the tenant.
# .env.local
TENANT_ORIGIN=scaffold.dev.lightbase.nl
TENANT_API_URL=http://localhost:3001
In Compas-backed projects generation of API code (hooks, api client) is provided out of the box.
The script handling generation is located in /scripts/generate.mjs
.
Generated code is available in /src/generated
.
.svg
files can be automatically optimized using SVGO and turned into React components by using the
yarn generate:svg
command.
SVG files can be placed in assets/svg
, the generated output is placed in src/assets/svg
.
yarn generate:svg
will not overwrite existing files in the src/assets/svg
directory to accommodate
changes. To re-generate an existing file, simply delete it and run yarn generate:svg
.
By default, React Query is configured with a staleTime
of 5 minutes and will retry a request a maximum of 3
times if the request failed due to a network error.
Previous versions of this scaffold shipped with Formik. However, time has learned us that react-hook-form is a much better fit for us and our projects.
These headers are configured by default, because they're considered good security practice. You can overwrite
these headers by
setting the header yourself in next.config.js
.
Header | Value |
---|---|
X-Frame-Options |
deny |
X-Content-Type-Options |
nosniff |
Referrer-Policy |
same-origin |
Strict-Transport-Security |
max-age=31536000 |
Permissions-Policy |
interest-cohort=() |
CSP allows restricting what sources assets (CSS, JavaScript, Fonts, etc.) can be loaded from.
By default, the following policy is set in /src/middleware.api.ts
:
Policy | Value |
---|---|
default-src |
'self' |
frame-ancestors |
'none' |
style-src |
'self' 'unsafe-inline' fonts.googleapis.com |
font-src |
'self' fonts.gstatic.com |
script-src |
'self' |
*Note: In development, the policy is more lax to allow Next.js to provide developer tooling.*
A check is run on yarn build
to make sure that the output does not contain any non-ES5 JavaScript code. This
is done so your app does not unexpectedly break in older browsers.
When offending output is found the build fails, and you are notified.
[PRESET] Checking browser compatibility...
[PRESET]
You might want to add the following entries to `preset.transpileModules` in `next.config.js`:
- yup
For more information, see: https://github.com/martpie/next-transpile-modules
Most modules can be transpiled. Modules that can't be transpiled can be ignored. Be sure to check if the module can be safely ignored or if you need to take additional steps per the modules' instructions.
Modules to be transpiled can be added by setting the preset.transpileModules
option in next.config.js
.
const { withPreset } = require("@lightbase/next-preset");
/** @type {import("next").NextConfig} */
module.exports = withPreset({
...
preset: {
transpileModules: [
"yup",
"dequal",
],
...
},
...
});
Some modules produce non-ES5 code and cannot be transpiled, e.g. Mapbox-GL. Usually the module does not support older browsers, so it does not make sense for them to transpile to ES5 or support it.
Modules to be ignored can be added by setting the preset.ignoreModules
option in next.config.js
.
const { withPreset } = require("@lightbase/next-preset");
/** @type {import("next").NextConfig} */
module.exports = withPreset({
...
preset: {
ignoreModules: ["mapbox-gl"],
...
},
...
});
Sentry is set up for error monitoring on both
server and client side. It is called from the <ErrorBoundary>
in _app.page.tsx
.
You can use translateErrorKeyOrReport
from src/lib
to automatically report missing error key translations.
A dynamic /robots.txt
is powered via /api/robots
which defaults to disallowing all agents
There's a simple health check API route: /api/_health
. In production, this API route can be used by DevOps
tools to determine whether the Next.js server is running or not.