This is a Strapi v5 project.
- Strapi 5
- TypeScript
- Docker
- Postgres 16 alpine (in local docker container)
- @strapi/plugin-color-picker
- @strapi/plugin-i18n
- @strapi/plugin-sentry
- @strapi/plugin-users-permissions
- @strapi/provider-email-mailgun
- @strapi/provider-email-nodemailer
- @strapi/provider-upload-aws-s3
- strapi-plugin-config-sync
- qs
- lodash
- pg
Copy & rename .env.example to .env and fill or update the values (most of the values are already set to default values, but you probably want to tweak them for your needs).
Preferred way of running Strapi locally is to run Postgres in docker container and Strapi locally.
(nvm use) # switch node version
(pnpm i) # deps should be installed running `pnpm install` in the root
# start both services in 1 command [easiest way]
pnpm run devor
(nvm use) # switch node version
(pnpm i) # deps should be installed running `pnpm install` in the root
# start Postgres in docker container
docker compose up -d db
# start Strapi locally
pnpm run developAnother way is to run Strapi in docker container too. Currently, an available Strapi Dockerfile is prepared only for production run (see below).
- Strapi runs on http://localhost:1337
- Admin panel is available on http://localhost:1337/admin
- Postgres runs on http://localhost:5432
There is strapi-export.tar.gz file in this directory with required initial data. You should import it to your local database with:
# in this directory
pnpm run importOtherwise, you have to create following manually in Strapi admin panel:
- Create one page in
Pagecollection with fullPath and slug =/(home page) - Create navbar in
Navbarsingle type - Create footer in
Footersingle type
Because FE supports 2 languages (en and cs) by default, you have to enable both languages in Strapi (Settings > Internationalization). Then you need to create corresponding content for both languages.
Go to Strapi admin panel and navigate to Settings > Config Sync > Tools. Click on "Import" button to import the configuration from files. More info about config sync is below.
To build and run Strapi in Docker container use Dockerfile prepared for production environment. It follows Strapi official documentation and recommended way of running app in Turborepo monorepo structure.
Warning
Note, that Turborepo requires access to root package.json, pnpm-lock.yaml and turbo.json files so you have to build it within whole monorepo context - run docker build from monorepo root.
More info here.
# from monorepo root
# build image, name and tag it
docker build -t starter-strapi:latest -f apps/strapi/Dockerfile .
# or build image and set APP_URL build arg to override localhost:1337 (default public URL of Admin panel)
docker build -t starter-strapi:latest -f apps/strapi/Dockerfile --build-arg APP_URL=https://cms.strapi-domain.dev .# run container using image
docker run -it --rm --name starter-strapi -p 1337:1337 --env-file apps/strapi/.env starter-strapi:latestTo change port, set PORT env variable in .env file and in docker run command (-p flag means port mapping between host:container).
Strapi requires Postgres database to run before it starts. There is no production docker-compose.yml file prepared with both services connected. Usually they are run separately (database in one container or in cloud servise, Strapi in another container).
A) If you have Postgres connection string or credentials, set them in .env file before running Strapi container. Example:
# .env
DATABASE_URL=postgres://user:password@host:port/database
# or use separate variables like DATABASE_NAME, DATABASE_HOST, etc.
B) To connect 2 different containers (Strapi and Postgres) both running in Docker, you have to create a network and connect both containers to that network. Here is an example of how to do it locally:
# run Postgres in docker - you can use docker-compose.yml from this directory
docker compose up -d db
# set DATABASE_HOST or DATABASE_URL for Strapi in .env file - host should be set to "db" (name of the Postgres service in docker-compose.yml) or to IP of the host machine instead of "0.0.0.0"
DATABASE_HOST=db
# run Strapi in docker and connect to same network. In docker-compose.yml there is a "db_network" network already defined, so you don't have to create it. Just reference it in run command
docker run -it --rm --name starter-strapi -p 1337:1337 --env-file apps/strapi/.env --network=strapi-next-starter_db_network starter-strapi:latestDocumentation is in /docs/pages-hierarchy.md
The utilities.link component is used as the StrapiLink component within the frontend page builder.
It provides a unified abstraction for handling both internal and external links while maintaining consistency with the design system.
- Supports icons, sizes, and visual variants based on shadcn/ui
- Handles external links via a simple text (URL) field
- Handles internal links as relations to Strapi collection types, ensuring link stability when a page
fullPathchanges and preventing broken links
All plugins are configured in config/plugins.ts file. Some of them may require additional setting of API keys or different ENV variables. User-permissions, seo and config-sync plugins are enabled by default and don't require additional configuration.
Tu enable Sentry plugin, set SENTRY_DSN to environment variables. By default, Sentry runs only in production mode, but you can change it in config/plugins.ts file.
Sentry service can be used in Strapi controllers and services as any other service. Uncaught errors are logged automatically. More information can be found in Sentry and Strapi docs.
// example of how to use Sentry in controller
async find(ctx) {
// this will log error to Sentry automatically
throw new Error("Not implemented")
// get sentry service
const sentry = strapi.plugin("sentry").service("sentry")
// manual error logging
sentry.sendError(new Error("My custom error"))
// get direct access to the Sentry instance
const instance = sentry.getInstance()
// call captureMessage or other Sentry functions
// pay attention, instance is undefined if Sentry is disabled (during development)
instance?.captureMessage("My custom message")
return []
},The starter supports OAuth authentication through Strapi's Users & Permissions plugin. Users can sign in with providers like GitHub, Google, Facebook, etc.
How it works:
- User clicks "Sign in with GitHub" (or other provider) on the frontend
- Frontend redirects to Strapi's OAuth endpoint:
/api/connect/{provider} - Strapi handles OAuth flow and redirects back to frontend with access token
- Frontend syncs the OAuth session with Strapi via
/auth/strapi-oauth/callback - User is authenticated and session is created
Setup:
- Go to Strapi admin panel: Settings > Users & Permissions > Providers
- Enable your desired provider (e.g., GitHub)
- Configure the provider:
- Client ID and Client Secret: Get these from your OAuth provider (e.g., GitHub Developer Settings)
- Redirect URL: Your frontend callback URL (e.g.,
https://your-domain.com/auth/strapi-oauth/callback)
- In your OAuth provider's settings (e.g., GitHub OAuth App):
- Homepage URL: Your Strapi URL (e.g.,
https://your-domain.com) - Authorization callback URL:
https://your-domain.com/api/connect/github/callback
- Homepage URL: Your Strapi URL (e.g.,
- The frontend automatically handles the OAuth flow - no additional configuration needed
Local testing with ngrok:
localhost URLs. You must use ngrok or a similar tunneling service for local development.
-
Install ngrok:
brew install ngrok(or download from ngrok.com) -
Start your Strapi backend:
yarn dev:strapi -
In another terminal, tunnel to Strapi:
ngrok http 1337
-
Copy the generated ngrok URL (e.g.,
https://abc123.ngrok.io) -
Update
apps/strapi/config/server.ts:url: "https://abc123.ngrok.io" -
Update
apps/strapi/src/admin/vite.config.ts:server: { allowedHosts: ["abc123.ngrok.io"] }
-
Set environment variables:
# apps/strapi/.env APP_URL=https://abc123.ngrok.io -
Update your OAuth provider (GitHub) with the ngrok URLs:
- Homepage URL:
https://abc123.ngrok.io - Authorization callback URL:
https://abc123.ngrok.io/api/connect/github/callback
- Homepage URL:
-
In Strapi admin (Settings > Users & Permissions > Providers > GitHub):
- Redirect URL: Your frontend callback (e.g.,
http://localhost:3000/auth/strapi-oauth/callback)
- Redirect URL: Your frontend callback (e.g.,
-
Restart both backend and frontend
See the Strapi GitHub provider documentation for more details.
Supported providers:
Any provider supported by Strapi's Users & Permissions plugin (GitHub, Google, Facebook, Discord, etc.). The frontend code in apps/ui/src/app/[locale]/auth/signin/_components/SignInForm.tsx shows how to add additional provider buttons.
strapi-plugin-config-sync plugin is installed by default to sync configuration between environments.
Strapi offers fine-grained control over population of data coming from the API. Our Strapi Client is fully typed and offers suggestions, but there are instances where you may want to avoid this. For example, the dynamic zone in collection types includes many components. Serializing the full population object for this DZ would yield an URL that's too long and may cause issues.
To overcome this issue, this project uses a document middleware. This allows you to control which relations are deeply populated on a per-request basis, optimizing data fetching for complex page structures.
How it works:
- The middleware is registered in
apps/strapi/src/index.ts. - The middleware is implemented in
apps/strapi/src/documentMiddlewares/page.ts. - Populate config is based on folders respecting Strapi structure and naming in
apps/strapi/src/populateDynamicZone. - To trigger custom population, your request must include the following in the query parameters:
populateDynamicZone: object with dynamic zones to ensure proper typing, each corresponding to a relation or field you want to populate.
Example request:
await PublicStrapiClient.fetchOneByFullPath("api::page.page", fullPath, {
locale,
populateDynamicZone: { content: true }, // ensures the middleware is triggered and the populate object is replaced
})- The middleware will map each key from
populateDynamicZoneattribute to the corresponding population rules in populateDynamicZone config, and apply them to the query. - This enables fine-grained, dynamic control over which relations and nested fields are included in the response.
Other collections Feel free to create your own middlewares for collections where you may need deep-population without specifying it in the request itself. This may be useful for large collections.
Pitfalls This requires active maintenance, as any changes to collections (i.e. the DZ in Page collection) will need to be reflected in the populate middleware. There is an alternative of using a deep-populate middleware, however this is STRONGLY discouraged. That's also why we removed it, despite using it in this project initially.
'populateDynamicZone' does not alter the types, so using it by itself will result in a type that does not include relations or dynamic zones. This is why we also include it in the populate object.
Typescript is used in this template. Typings schemas are generated automatically after code change by Strapi (based on configuration in config/typescript.ts and stored in types/generated/* as ts definition files. Do not modify them manually, let Strapi do it. Don't forget to version them in git.
Warning
By enabling and generating types in Strapi, the API and models on the frontend are typed out-of-box through @repo/strapi-types workspace package. By turning it off, the code related to the API on the frontend will have to be modified.
In src/index.ts there are prepared hooks for sending email to admin and user after registration (afterCreate hook). By default they are disabled (commented out). Before enabling them, you have to turn on email plugin in config/plugins.ts and provide required ENV variables (without email service emails won't work and Strapi will die with every user registration).
Data can be easily transferred between environments in multiple ways. Check out official docs https://docs.strapi.io/dev-docs/data-management. Scripts are prepared in package.json see export:all, export:content, import, transfer.
Edit config/cron-tasks.ts to add cron jobs. Enable them by setting CRON_ENABLED=true in .env file.
A simple health check endpoint is available at /api/health, e.g. http://localhost:1337/api/health. This can be used for monitoring and uptime checks.
This starter supports Strapi's new feature: Previews. It works by embedding an iframe of the frontend application directly inside the editor.
In order to enable the feature, you need to configure the following environmental variables:
STRAPI_PREVIEW_ENABLED:trueto enable, otherwise disabledCLIENT_URL: Absolute URL of the frontend application (http://127.0.0.1:3000 for local development in most cases)STRAPI_PREVIEW_SECRET: shared secret between frontend and backend, which is used to authenticate if the preview can be viewed. If configured correctly, you should be able to visit previews for content types that have been implemented in thepreview.configconfiguration object inconfig/admin.ts.