Official website for Cloud Native Days in Norway.
This project uses mise for tool management and task running, and fnox for secure secret storage in your local keychain.
Install mise (if not already installed):
curl https://mise.run | shInstall fnox:
brew install fnoxmise run install
# equivalent to: pnpm installSecrets are stored securely in your OS keychain via fnox — no plaintext .env.local needed after setup.
All secrets are declared in fnox.toml. To populate the keychain for the first time, create a temporary .env.local with your secrets and run:
./scripts/migrate-fnox.sh
rm .env.local # safe to delete once importedTo add or update an individual secret later:
fnox set MY_SECRET_KEY -- "the value here"To verify what is stored in the keychain:
fnox listNote: We also maintain committed default
.env,.env.test, and.env.productionfiles containing safe non-sensitive defaults (public URLs, feature flags). These exist for CI runners and contributors who don't usemise.
mise run dev
# equivalent to: fnox exec -- pnpm run devFinally, open http://localhost:3000 in your browser to view the website.
| Task | Description |
|---|---|
mise run dev |
Start the development server |
mise run build |
Build the production bundle |
mise run test |
Run the full test suite (uses test keychain secrets) |
mise run test-watch |
Run tests in watch mode |
mise run test-badges |
Run badge-specific tests |
mise run check |
Run all checks: lint, typecheck, format, knip |
mise run lint-fix |
Run ESLint with auto-fix |
mise run format |
Auto-format all files with Prettier |
mise run typecheck |
Run TypeScript type checking |
mise run all |
Run checks, tests, and build sequentially |
mise run storybook |
Start Storybook dev server |
mise run storybook-test |
Build and test Storybook stories (CI mode) |
mise run sanity -- <cmd> |
Run any Sanity CLI command |
mise run migrate -- <name> |
Run a Sanity content migration |
mise run migrate-create -- "<desc>" |
Create a new Sanity migration |
mise run migrate-validate |
Validate documents against current schema |
mise run manage-files |
Manage orphaned uploaded files |
mise run clean |
Remove build artefacts (.next, storybook-static) |
mise run install |
Install pnpm dependencies |
This project uses Turbopack for both development and production builds to ensure consistent CSS generation with Tailwind CSS v4. A known incompatibility exists between Turbopack (used in dev) and Webpack (Vercel's default production bundler) that causes Tailwind classes to be missing in production.
The fix is already in place via experimental.turbo: {} in next.config.ts.
This project includes VS Code workspace settings in .vscode/settings.json that automatically configure:
- Prettier formatting using the project's
prettier.config.jssettings - Format on save for consistent code style
- ESLint integration with auto-fix on save
- Tailwind CSS IntelliSense for better development experience
The project includes recommended VS Code extensions in .vscode/extensions.json:
- Prettier - Code formatter (
esbenp.prettier-vscode) - Tailwind CSS IntelliSense (
bradlc.vscode-tailwindcss) - TypeScript and JavaScript Nightly (
ms-vscode.vscode-typescript-next) - Sanity.io (
sanity-io.vscode-sanity)
VS Code will prompt you to install these extensions when you open the workspace.
The project uses Prettier for code formatting with these settings:
- Single quotes (
') instead of double quotes - No semicolons
- Tailwind CSS class sorting via
prettier-plugin-tailwindcss
To format all files manually:
pnpm run formatTo check for linting issues:
pnpm run lintTo run TypeScript type checking:
pnpm run typecheckTo run all checks at once (typecheck, lint, knip, format — runs in parallel):
pnpm run checkESLint and Prettier use --cache flags so subsequent runs only process changed files. First run after a clean clone takes ~40s; warm runs take ~5s.
This project uses simple-git-hooks to run pnpm run check and pnpm run test before every commit.
After cloning the repo and installing dependencies, activate the hooks:
pnpm exec simple-git-hooksTo skip the hook for a one-off commit:
SKIP_SIMPLE_GIT_HOOKS=1 git commit -m "wip"If the hook configuration in package.json changes, re-run pnpm exec simple-git-hooks to update.
Install the Sanity CLI:
pnpm install --global sanity@latestDeploy Sanity Studio to Sanity.io
sanity deployModels are defined in lib/<type>/types.ts and in sanity/schemaTypes/<type>.ts for the representation in Sanity Studio.
The project includes migration scripts based on Sanity's migration framework that help update content data when schemas change. Migrations are stored in migrations/.
To create a new migration, use the Sanity CLI command:
npx sanity@latest migration create "Replace event type with event format"Replace the text in quotes with a descriptive title for your migration. This will create a new migration folder with a boilerplate script that you can modify.
Before running any migration, export your dataset as a backup:
# Create a backup of your dataset
npx sanity@latest dataset export production my-backup-filename.tar.gzThis gives you a safety net in case anything goes wrong during the migration.
After making schema changes, validate your documents against the new schema:
# Validate documents against schema changes
npx sanity@latest documents validate -yThis helps identify any potential issues before running the migration.
After creating a backup and validating documents, run the migration:
# Run a migration with Sanity CLI
npx sanity@latest migration run add-required-conference-reference-to-talksWhen prompted, provide your Sanity auth token. The migration will process the documents and report the changes made.
For more details about available migrations and creating new ones, see Sanity Migrations Documentation.
To learn more about Sanity migrations, check out these resources:
Authentication is handled by next-auth. The required secrets are managed via fnox in your local keychain (see Set up secrets above).
To generate a new AUTH_SECRET:
openssl rand -base64 32 | fnox set AUTH_SECRET --To add a new provider, add it to lib/auth.ts and store the credentials in the keychain:
fnox set AUTH_MY_PROVIDER_ID -- "your-id"
fnox set AUTH_MY_PROVIDER_SECRET -- "your-secret"Also declare the new keys in fnox.toml under [secrets] so other developers know to populate them.
fnox set AUTH_GITHUB_ID -- "your-github-id"
fnox set AUTH_GITHUB_SECRET -- "your-github-secret"All environment variables are declared in fnox.toml and stored in your OS keychain. The table below lists the key variables and where to get them:
| Variable | Description | Where to get it |
|---|---|---|
AUTH_SECRET |
NextAuth session secret | openssl rand -base64 32 |
AUTH_GITHUB_ID / AUTH_GITHUB_SECRET |
GitHub OAuth | GitHub Developer Settings |
AUTH_LINKEDIN_ID / AUTH_LINKEDIN_SECRET |
LinkedIn OAuth | LinkedIn Developer Portal |
SANITY_API_TOKEN_READ |
Sanity read token | Sanity project settings |
SANITY_API_TOKEN_WRITE |
Sanity write token | Sanity project settings |
RESEND_API_KEY |
Email sending (Resend) | Resend dashboard |
BLOB_READ_WRITE_TOKEN |
Vercel Blob storage | Vercel dashboard → Storage → Blob |
BADGE_ISSUER_RSA_PRIVATE_KEY |
OpenBadges JWT signing key | Generate with openssl genrsa 2048 |
BADGE_ISSUER_RSA_PUBLIC_KEY |
OpenBadges JWT verification key | Extract from the private key |
NEXT_PUBLIC_EXCHANGE_RATE_API_KEY |
Exchange rates (optional) | ExchangeRate-API.com (free tier: 1,500 req/month) |
If NEXT_PUBLIC_EXCHANGE_RATE_API_KEY is not set, the system uses fallback exchange rates. See docs/EXCHANGE_RATE_API.md for details.
The project uses a two-tier storage architecture for handling proposal attachments (slides, resources):
- Temporary Storage: Vercel Blob (client-side direct upload, bypasses 4.5MB serverless limit)
- Permanent Storage: Sanity CMS (asset management)
Files up to 50MB are supported. See docs/ATTACHMENT_STORAGE.md for detailed architecture documentation.
- Create Vercel Blob store in Vercel Dashboard → Storage → Blob
- Pull environment variables:
npx vercel env pull .env.local BLOB_READ_WRITE_TOKENis automatically created
The system includes automatic cleanup of temporary files via daily cron job.
This project is licensed under the MIT License. See the LICENSE file for more information.
To learn more about the technologies used in this site template, see the following resources:
- Tailwind CSS - the official Tailwind CSS documentation
- Next.js - the official Next.js documentation
- Headless UI - the official Headless UI documentation