Landing page for the Studánky mobile app, built with Next.js App Router and Tailwind CSS.
Feature docs describing what the web app supports:
- Deep Linking — Universal Links & App Links — how shared
/s/{id}links open in the native app, with a platform-aware web fallback that previews the shared spring (fetched from Strapi) and links to the stores. - App Store / Play Store Banners — site-wide native-app promotion: iOS Smart App Banner, custom Android banner, and the manifest
related_applicationssignal. - Strapi share endpoint (backend brief) — the contract for the public Strapi
previewendpoint that feeds the/s/{id}fallback; a handoff for the backend/Strapi team. - Newsletter signup — frontend-to-Strapi contract for the public newsletter form and abuse-prevention notes.
Deep linking (Universal Links / App Links) + /s/* fallback page:
-
Real store URLs — in
src/config/site.ts(links.appStore,links.googlePlay) replace the#app-store/#google-playplaceholders with the real App Store and Google Play links. They propagate to the landing page badges and the fallback page. -
Verify Android fingerprints —
src/app/.well-known/assetlinks.json/route.tsmust include the App signing key SHA-256 from the Play Console (App integrity), not just the upload/debug key. -
Verify iOS appID —
TEAMID.BundleIDinsrc/app/.well-known/apple-app-site-association/route.tsmust match the Apple Developer account and theapplinks:studankyapp.czentitlement. -
iOS Smart App Banner — set the numeric App Store ID (not the bundle ID) in
src/config/site.ts(appStoreId) once the app is published. Empty = banner is not rendered. -
Android app banner —
src/config/site.ts(androidPackageId) is pre-filled withcz.studankyapp.studanky; verify the Play listing is live (otherwise "Open" leads to a non-existent page). Empty = disables both the banner and the manifestrelated_applications. -
Verify endpoints after deploy (must return
200,application/json,redirs=0):curl -sS -o /dev/null -w "%{http_code} ct=%{content_type} redirs=%{num_redirects}\n" \ https://studankyapp.cz/.well-known/apple-app-site-association curl -sS -o /dev/null -w "%{http_code} ct=%{content_type} redirs=%{num_redirects}\n" \ https://studankyapp.cz/.well-known/assetlinks.json
-
Coolify / Traefik — ensure the apex
studankyapp.czserves.well-knownwithout redirecting towww(otherwise deep links break); valid HTTPS cert; no basic-auth in front of.well-known. -
STRAPI_API_BASEenv — set it in Coolify to the public Strapi API base (incl./api) so newsletter signup works and the/s/{id}fallback shows real spring data. Confirm the endpoint contracts with the Strapi team (docs/newsletter.md, docs/strapi-share-endpoint.md).
- Next.js 16
- React 19
- TypeScript
- Tailwind CSS 4
- pnpm
Local values go in .env.local (git-ignored). Copy the template and fill it in:
cp .env.example .env.local| Variable | Required | Purpose |
|---|---|---|
STRAPI_API_BASE |
Yes | Base URL of the public Strapi API (incl. /api). Used by newsletter signup and by the deep-link preview page — see docs/newsletter.md and docs/strapi-share-endpoint.md. Without it, newsletter signup fails and /s/{id} degrades to a generic install page. |
Production values are configured in Coolify, not in a committed file. See
.env.example for the documented shape.
The production build uses Next.js standalone output and starts the traced server
with node .next/standalone/server.js.
Coolify Nixpacks settings:
Build Pack:NixpacksPorts Exposes:3000Is it a static site?: disabledSTRAPI_API_BASE: set as a runtime environment variable
The repository nixpacks.toml pins Node.js 22, activates pnpm 11 via Corepack,
copies the standalone static assets, and overrides the start command.
cp .env.example .env.local # first time only
pnpm devOpen http://localhost:3000 with your browser to see the result.
pnpm lint
pnpm typecheck
pnpm check
pnpm buildsrc/
├── app/ # App Router; localized routes under app/[locale]/
├── components/
│ ├── landing/ # Marketing page composition and sections
│ ├── layout/ # Header, footer, mobile nav, app badges, language switcher
│ └── ui/ # shadcn/ui source components
├── config/ # Site URL, navigation, global links
├── i18n/ # Locale config, dictionary loading, request helpers
├── lib/ # Shared utilities and server helpers
├── types/ # Shared TypeScript contracts
└── proxy.ts # Locale detection + prefix redirects (Next.js proxy)
messages/ # Translation catalogs, one JSON per locale — see messages/README.md
public/
├── app/ # Screenshots, store badges, QR code
├── brand/ # Logo and brand marks
└── social/ # Static social sharing assets
docs/ # Feature documentation (deep linking, app banners)
Landing copy is localized: all user-facing text lives in messages/<locale>.json (typed by Dictionary in src/i18n/) and is rendered from Server Components, so catalogs never reach the client bundle. Dynamic Strapi-backed content can be added later from Server Components without exposing API tokens to the browser.