Skip to content

feat: add frontend Dockerfile and update docker-compose files for fro…#20

Closed
chakrihacker wants to merge 17 commits into
mainfrom
feat/expo-web-hosting
Closed

feat: add frontend Dockerfile and update docker-compose files for fro…#20
chakrihacker wants to merge 17 commits into
mainfrom
feat/expo-web-hosting

Conversation

@chakrihacker

@chakrihacker chakrihacker commented Sep 5, 2025

Copy link
Copy Markdown
Contributor

…ntend service

Note

I'm currently writing a description for your pull request. I should be done shortly (<1 minute). Please don't edit the description field until I'm finished, or we may overwrite each other. If I find nothing to write about, I'll delete this message.

Summary by Sourcery

Containerize the frontend application using Bun and expo/server, add a server entrypoint to serve builds, update Docker Compose configurations to include the frontend service, and introduce build scripts along with minor code and Dockerfile cleanup

New Features:

  • Add a multistage Dockerfile for the frontend app with a distroless final image
  • Introduce a Bun-based server entrypoint (server.ts) to serve expo exports and static assets

Enhancements:

  • Add frontend build scripts (export:web and export:server) and @expo/server dependency
  • Refactor SignUpContainer layout and formatting for consistency
  • Improve API Dockerfile caching by adjusting package copy directives

Deployment:

  • Register the frontend service in both docker-compose.dev.yml and docker-compose.prod.yml with build context, ports, env_file, and volume settings

@sourcery-ai

sourcery-ai Bot commented Sep 5, 2025

Copy link
Copy Markdown

Reviewer's Guide

This PR introduces a Bun-based Docker build and runtime pipeline for the frontend (including a multi-stage Dockerfile, a custom Bun server script, and docker-compose updates), enhances package scripts/dependencies, and includes a minor UI container refactor and API Dockerfile caching tweak.

Flow diagram for Bun-based frontend server request handling

flowchart TD
  A["Incoming HTTP Request"]
  B["Bun.serve() routes"]
  C["/_expo/static/* or /assets/*"]
  D["Check file existence in ./dist/client"]
  E["Serve file if exists"]
  F["404 Not Found if missing"]
  G["/* (other routes)"]
  H["createRequestHandler (SSR)"]
  I["/canvaskit.wasm"]
  J["Serve canvaskit.wasm if exists"]
  K["404 Not Found if missing"]

  A --> B
  B --> C
  C --> D
  D -- exists --> E
  D -- missing --> F
  B --> G
  G --> H
  B --> I
  I --> J
  I --> K
Loading

File-Level Changes

Change Details Files
Refactor SignUpContainer formatting and prop ordering
  • Normalize indentation and alignment
  • Reorder SignUpSuccessCard props for consistency
apps/frontend/src/containers/auth/sign-up.container.tsx
Add multi-stage Dockerfile for frontend with Bun build and distroless runtime
  • Define base stage for dependency installation
  • Build stage exporting web and server bundles
  • Runtime stage using distroless image and CMD to launch server
apps/frontend/Dockerfile
Add Bun-based server.ts to serve Expo static assets
  • Implement Bun.serve with routes for static files and Expo handler
  • Log server URL on startup
apps/frontend/server.ts
Integrate frontend service into Docker Compose for dev and prod
  • Add 'frontend' service in dev compose with build context, ports, volumes, and env_file
  • Mirror 'frontend' service configuration in prod compose
docker/docker-compose.dev.yml
docker/docker-compose.prod.yml
Enhance frontend package.json with export scripts and server dependency
  • Add export:web and export:server scripts
  • Introduce @expo/server dependency
apps/frontend/package.json
Optimize API Dockerfile caching by commenting out redundant copy
  • Comment out the COPY packages/ line to avoid duplicating dependencies
apps/api/Dockerfile

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Sep 5, 2025

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Dockerized frontend with a Bun-based server and web export support.
    • Added secure credential storage via Expo Secure Store plugin.
  • Improvements

    • Upgraded Expo packages for stability and compatibility.
    • Default API endpoint updated to the staging environment.
  • DevOps/CI

    • Frontend build added to CI with image publishing.
    • New frontend service in dev/prod docker-compose.
  • Chores

    • Dependency updates (including Prisma).
    • Added Docker ignore rules for faster, leaner builds.
    • Renamed frontend package for clarity.

Walkthrough

Adds a Bun-based frontend service (server, Dockerfile, .dockerignore, package changes, env updates), registers frontend in dev/prod compose files, adjusts API Dockerfile to stop copying the full packages/ directory in the base stage, and extends CI to detect/build/push the frontend image.

Changes

Cohort / File(s) Summary
Frontend Docker & Server
apps/frontend/Dockerfile, apps/frontend/server.ts
Adds a Bun-based frontend Dockerfile and a Bun HTTP server that serves static assets from dist/client and forwards other requests to an Expo handler (dist/server), listening on port 4000.
Compose entries
docker/docker-compose.dev.yml, docker/docker-compose.prod.yml
Adds a frontend service (build context .., dockerfile ./apps/frontend/Dockerfile) exposing 4000:4000; dev compose mounts source volumes and depends on api.
Frontend package & scripts
apps/frontend/package.json
Renames package to @universal/super-app, adds export:web and export:server scripts, adds @expo/server dependency, and bumps several Expo-related dependencies.
Frontend env config
apps/frontend/.env, apps/frontend/.env.example
Updates EXPO_PUBLIC_API_URL from https://universal-starter.onrender.com to https://staging-universal-api.coolify.fyndx.io.
Frontend .dockerignore
apps/frontend/.dockerignore
Adds comprehensive ignore patterns to reduce Docker build context (node_modules, caches, env files, editor/OS artifacts, framework build outputs).
CI: Docker build workflow
.github/workflows/docker-build.yml
Adds frontend path triggers and change detection output, introduces a build-frontend job gated on frontend changes to build/push the frontend image using apps/frontend/Dockerfile, and aligns tag prefix formatting for other images.
API Dockerfile tweak
apps/api/Dockerfile
Comments out COPY packages/ ./packages/ in the base stage while keeping per-package package.json copy lines.
Expo config
apps/frontend/app.json
Adds expo-secure-store to the Expo plugins array.
Formatting-only UI change
apps/frontend/src/containers/auth/sign-up.container.tsx
JSX prop reordering and formatting adjustments only; no behavioral changes.
Prisma bump
packages/db/package.json
Bumps prisma devDependency and @prisma/client dependency from ^6.15.0 to ^6.16.2.

Sequence Diagram(s)

sequenceDiagram
  participant B as Browser
  participant S as Bun Frontend Server (port 4000)
  participant C as Dist Client (static files)
  participant E as Expo Handler (dist/server)

  B->>S: HTTP request
  alt Static asset (/ _expo/static/*, /assets/*, /canvaskit.wasm)
    S->>C: Check file existence (Bun.file(...).exists)
    alt exists
      S-->>B: 200 Serve file
    else
      S-->>B: 404 Not found
    end
  else Dynamic route (other paths)
    S->>E: Forward request to Expo handler
    E-->>S: Handler response
    S-->>B: Proxy response
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I twitch my whiskers, code in flight,
Bun hums a server through the night.
Expo hands pages, static and bright,
Compose stacks services, ports alight.
Rabbit hops—containers ready, hold tight! 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the main change in the PR by highlighting the addition of a frontend Dockerfile and the updates to Docker Compose files for the frontend service, which matches the core changes in the patch.
Description Check ✅ Passed The description clearly outlines the containerization of the frontend using Bun and expo/server, the addition of a server entrypoint, updates to Docker Compose configurations, introduction of build scripts, and minor code cleanups, all of which align with the changes in the PR.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/expo-web-hosting

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3a52f7 and 69d7426.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • apps/frontend/.dockerignore (1 hunks)
  • apps/frontend/Dockerfile (1 hunks)
  • packages/db/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/frontend/.dockerignore
  • apps/frontend/Dockerfile
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (1)
packages/db/package.json (1)

18-24: Prisma + client bump stays in sync

Glad to see both prisma and @prisma/client moving together to 6.16.2—this patch specifically fixes the unref() regression that hit non-Node runtimes, so it should smooth out our Bun-based flows without extra changes.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 Security concerns

Directory traversal:
The static asset handlers build paths from request URLs without explicit sanitization. While Bun.file likely treats the string literally, consider normalizing the pathname (e.g., using new URL(...).pathname and path.join with a resolved base plus a check to keep the final path within dist/client) to mitigate attempts like /assets/../server.js.

⚡ Recommended focus areas for review

Static path handling

The static file routes concatenate the request pathname directly into the filesystem path; validate that path normalization prevents directory traversal and that leading slashes result in the intended relative paths under ./dist/client.

"/_expo/static/*": async (request) => {
  const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
  const exists = await file.exists();
  return exists
    ? new Response(file)
    : new Response("Not found", { status: 404 });
},
"/assets/*": async (request) => {
  const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
  const exists = await file.exists();
  return exists
    ? new Response(file)
    : new Response("Not found", { status: 404 });
},
Distroless entrypoint

The final image uses distroless base with CMD ["./server"]; confirm the compiled binary path and executable permissions exist in that stage, and that any runtime dependencies (e.g., certificates, timezone) needed by server-side fetch are available.

FROM gcr.io/distroless/base

WORKDIR /app

COPY --from=build /app/apps/frontend/dist ./dist
COPY --from=build /app/apps/frontend/server ./server

ENV NODE_ENV=production

CMD [ "./server" ]

EXPOSE 4000
Volume overlays

The frontend service mounts the app and packages over the installed node_modules; ensure bun install is compatible with bind mounts and hot reload expectations, or cache invalidation could cause module resolution issues.

frontend:
  build:
    context: ..
    dockerfile: ./apps/frontend/Dockerfile
  ports:
    - "4000:4000"
  env_file:
    - ../apps/frontend/.env
  volumes:
    - ../apps/frontend:/app/apps/frontend
    - ../packages:/app/packages
    - ../bun.lock:/app/bun.lock
    - ../package.json:/app/package.json
  depends_on:
    - api

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `apps/frontend/server.ts:10` </location>
<code_context>
+const server = Bun.serve({
+  port: 4000,
+  routes: {
+    "/_expo/static/*": async (request) => {
+      const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
+      const exists = await file.exists();
</code_context>

<issue_to_address>
Static file serving does not set Content-Type headers.

Set the Content-Type header according to the file extension to ensure correct browser handling.

Suggested implementation:

```typescript
    "/_expo/static/*": async (request) => {
      const url = new URL(request.url);
      const filePath = `./dist/client/${url.pathname}`;
      const file = Bun.file(filePath);
      const exists = await file.exists();

      // Determine content type
      const ext = path.extname(filePath).toLowerCase();
      const contentTypeMap: Record<string, string> = {
        ".js": "application/javascript",
        ".css": "text/css",
        ".html": "text/html",
        ".json": "application/json",
        ".png": "image/png",
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".svg": "image/svg+xml",
        ".ico": "image/x-icon",
        ".webp": "image/webp",
        ".woff": "font/woff",
        ".woff2": "font/woff2",
        ".ttf": "font/ttf",
        ".map": "application/json",
      };
      const contentType = contentTypeMap[ext] || "application/octet-stream";

      return exists
        ? new Response(file, { headers: { "Content-Type": contentType } })
        : new Response("Not found", { status: 404 });

```

```typescript
    "/assets/*": async (request) => {
      const url = new URL(request.url);
      const filePath = `./dist/client/${url.pathname}`;
      const file = Bun.file(filePath);
      const exists = await file.exists();

      // Determine content type
      const ext = path.extname(filePath).toLowerCase();
      const contentTypeMap: Record<string, string> = {
        ".js": "application/javascript",
        ".css": "text/css",
        ".html": "text/html",
        ".json": "application/json",
        ".png": "image/png",
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".svg": "image/svg+xml",
        ".ico": "image/x-icon",
        ".webp": "image/webp",
        ".woff": "font/woff",
        ".woff2": "font/woff2",
        ".ttf": "font/ttf",
        ".map": "application/json",
      };
      const contentType = contentTypeMap[ext] || "application/octet-stream";

      return exists
        ? new Response(file, { headers: { "Content-Type": contentType } })
        : new Response("Not found", { status: 404 });

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread apps/frontend/server.ts
const server = Bun.serve({
port: 4000,
routes: {
"/_expo/static/*": async (request) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Static file serving does not set Content-Type headers.

Set the Content-Type header according to the file extension to ensure correct browser handling.

Suggested implementation:

    "/_expo/static/*": async (request) => {
      const url = new URL(request.url);
      const filePath = `./dist/client/${url.pathname}`;
      const file = Bun.file(filePath);
      const exists = await file.exists();

      // Determine content type
      const ext = path.extname(filePath).toLowerCase();
      const contentTypeMap: Record<string, string> = {
        ".js": "application/javascript",
        ".css": "text/css",
        ".html": "text/html",
        ".json": "application/json",
        ".png": "image/png",
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".svg": "image/svg+xml",
        ".ico": "image/x-icon",
        ".webp": "image/webp",
        ".woff": "font/woff",
        ".woff2": "font/woff2",
        ".ttf": "font/ttf",
        ".map": "application/json",
      };
      const contentType = contentTypeMap[ext] || "application/octet-stream";

      return exists
        ? new Response(file, { headers: { "Content-Type": contentType } })
        : new Response("Not found", { status: 404 });
    "/assets/*": async (request) => {
      const url = new URL(request.url);
      const filePath = `./dist/client/${url.pathname}`;
      const file = Bun.file(filePath);
      const exists = await file.exists();

      // Determine content type
      const ext = path.extname(filePath).toLowerCase();
      const contentTypeMap: Record<string, string> = {
        ".js": "application/javascript",
        ".css": "text/css",
        ".html": "text/html",
        ".json": "application/json",
        ".png": "image/png",
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".svg": "image/svg+xml",
        ".ico": "image/x-icon",
        ".webp": "image/webp",
        ".woff": "font/woff",
        ".woff2": "font/woff2",
        ".ttf": "font/ttf",
        ".map": "application/json",
      };
      const contentType = contentTypeMap[ext] || "application/octet-stream";

      return exists
        ? new Response(file, { headers: { "Content-Type": contentType } })
        : new Response("Not found", { status: 404 });

@korbit-ai korbit-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by Korbit AI

Korbit automatically attempts to detect when you fix issues in new commits.
Category Issue Status
Security Path Traversal Vulnerability in File Serving ▹ view
Error Handling Inconsistent Error Messages ▹ view
Readability Hard-coded Port Number ▹ view
Design Violates DRY principle in route handlers ▹ view
Design Violation of Single Responsibility Principle ▹ view
Files scanned
File Path Reviewed
apps/frontend/server.ts
apps/frontend/src/containers/auth/sign-up.container.tsx

Explore our documentation to understand the languages and file types we support and the files we ignore.

Check out our docs on how you can make Korbit work best for you and your team.

Loving Korbit!? Share us on LinkedIn Reddit and X

Comment thread apps/frontend/server.ts
port: 4000,
routes: {
"/_expo/static/*": async (request) => {
const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path Traversal Vulnerability in File Serving category Security

Tell me more
What is the issue?

The code doesn't sanitize or validate the pathname from the URL, which could lead to path traversal vulnerabilities.

Why this matters

Malicious users could potentially access files outside the intended directory by using '../' in the URL path.

Suggested change ∙ Feature Preview

Implement path validation and normalization before serving files:

const pathname = new URL(request.url).pathname;
const normalizedPath = path.normalize(pathname).replace(/^\.+/, '');
const file = Bun.file(`./dist/client/${normalizedPath}`);
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

Comment thread apps/frontend/server.ts
const exists = await file.exists();
return exists
? new Response(file)
: new Response("Not found", { status: 404 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent Error Messages category Error Handling

Tell me more
What is the issue?

Inconsistent error messages across different route handlers ('Not found' vs 'Not found here') could confuse client applications.

Why this matters

Different error messages for the same error condition (404) can make error handling more difficult for clients and complicate debugging.

Suggested change ∙ Feature Preview

Standardize error messages across all routes:

const NOT_FOUND_RESPONSE = new Response("Not found", { status: 404 });

// Then use it consistently in all routes
return exists ? new Response(file) : NOT_FOUND_RESPONSE;
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

Comment thread apps/frontend/server.ts
Comment on lines +7 to +8
const server = Bun.serve({
port: 4000,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded Port Number category Readability

Tell me more
What is the issue?

Hard-coded port number reduces configurability and readability.

Why this matters

Using magic numbers makes it harder to understand the significance of the value and complicates configuration changes across different environments.

Suggested change ∙ Feature Preview

Extract the port into a named constant or environment variable:

const PORT = process.env.PORT || 4000;

const server = Bun.serve({
  port: PORT,
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

Comment thread apps/frontend/server.ts
Comment on lines +10 to +23
"/_expo/static/*": async (request) => {
const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
const exists = await file.exists();
return exists
? new Response(file)
: new Response("Not found", { status: 404 });
},
"/assets/*": async (request) => {
const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
const exists = await file.exists();
return exists
? new Response(file)
: new Response("Not found", { status: 404 });
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Violates DRY principle in route handlers category Design

Tell me more
What is the issue?

Duplicate file serving logic appears in multiple route handlers

Why this matters

Code duplication increases maintenance burden and risk of inconsistencies when changes are needed

Suggested change ∙ Feature Preview

Extract the common file serving logic into a reusable function:

const serveStaticFile = async (request: Request) => {
  const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
  const exists = await file.exists();
  return exists
    ? new Response(file)
    : new Response("Not found", { status: 404 });
};

// Then use in routes:
routes: {
  "/_expo/static/*": serveStaticFile,
  "/assets/*": serveStaticFile,
  // ...
}
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

Comment thread apps/frontend/server.ts
Comment on lines +9 to +16
routes: {
"/_expo/static/*": async (request) => {
const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
const exists = await file.exists();
return exists
? new Response(file)
: new Response("Not found", { status: 404 });
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Violation of Single Responsibility Principle category Design

Tell me more
What is the issue?

Static file serving configuration is mixed with server setup code

Why this matters

Mixing concerns makes the code harder to maintain and configure, particularly when static file serving rules need to be modified

Suggested change ∙ Feature Preview

Extract static file serving configuration into a separate module:

// staticFileConfig.ts
export const staticFileRoutes = {
  "/_expo/static/*": serveStaticFile,
  "/assets/*": serveStaticFile,
  "/canvaskit.wasm": serveWasmFile
};

// server.ts
import { staticFileRoutes } from './staticFileConfig';

const server = Bun.serve({
  port: 4000,
  routes: {
    ...staticFileRoutes,
    "/*": async (request) => await handler(request),
  },
});
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

@qodo-code-review

qodo-code-review Bot commented Sep 5, 2025

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Fix runtime image and paths

The final stage uses gcr.io/distroless/base to run a Bun-compiled binary, which
will likely fail due to missing glibc and SSL certificates, and server.ts relies
on import.meta.dir that points to a build-time path that doesn’t exist in the
final image (/app/dist vs /app/apps/frontend/dist). Use a compatible runtime
(e.g., gcr.io/distroless/cc with ca-certificates or oven/bun) and resolve
asset/server paths relative to process.cwd() (or copy dist to match the compiled
path) so the server can start and serve assets reliably.

Examples:

apps/frontend/Dockerfile [26-35]
FROM gcr.io/distroless/base

WORKDIR /app

COPY --from=build /app/apps/frontend/dist ./dist
COPY --from=build /app/apps/frontend/server ./server

ENV NODE_ENV=production

CMD [ "./server" ]
apps/frontend/server.ts [5]
const handler = createRequestHandler(path.join(import.meta.dir, "dist/server"));

Solution Walkthrough:

Before:

# apps/frontend/server.ts
// Path is resolved at build time to /app/apps/frontend/dist/server
const handler = createRequestHandler(path.join(import.meta.dir, "dist/server"));

// ... server implementation

# apps/frontend/Dockerfile
...
# Final image
FROM gcr.io/distroless/base
WORKDIR /app

# Copies build artifacts to a different path structure
COPY --from=build /app/apps/frontend/dist ./dist
COPY --from=build /app/apps/frontend/server ./server

CMD [ "./server" ]

After:

# apps/frontend/server.ts
// Path is resolved at runtime relative to the current working directory
const handler = createRequestHandler(path.resolve(process.cwd(), "dist/server"));

// ... server implementation

# apps/frontend/Dockerfile
...
# Final image using a more suitable base
FROM oven/bun:latest
WORKDIR /app

# Paths in the final image now match what the server expects at runtime
COPY --from=build /app/apps/frontend/dist ./dist
COPY --from=build /app/apps/frontend/server ./server

CMD [ "./server" ]
Suggestion importance[1-10]: 10

__

Why: This suggestion correctly identifies two critical flaws: a path mismatch from using import.meta.dir which will cause the server to fail on startup, and an unsuitable distroless base image that lacks SSL certificates, which would cause runtime failures on API calls.

High
General
Add MIME type headers

Add proper MIME type headers for static assets to ensure browsers handle files
correctly. Without content-type headers, browsers may not render CSS, JS, or
other assets properly.

apps/frontend/server.ts [10-23]

 "/_expo/static/*": async (request) => {
   const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
   const exists = await file.exists();
   return exists
-    ? new Response(file)
+    ? new Response(file, { headers: { "Content-Type": file.type || "application/octet-stream" } })
     : new Response("Not found", { status: 404 });
 },
 "/assets/*": async (request) => {
   const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
   const exists = await file.exists();
   return exists
-    ? new Response(file)
+    ? new Response(file, { headers: { "Content-Type": file.type || "application/octet-stream" } })
     : new Response("Not found", { status: 404 });
 },
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that serving static files without a Content-Type header can cause browser rendering issues and proposes a valid fix using Bun.file.type.

Medium
  • Update

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/Dockerfile (1)

51-51: Do not bake .env into the runtime image (secrets leakage).

Copying .env* into the final image risks leaking credentials. Pass env via orchestrator (compose/secrets) at runtime.

-# Copy .env file to runtime image for Prisma
-COPY --from=build /app/apps/api/.env* ./

Compose example (prod):

services:
  api:
    environment:
      - DATABASE_URL=${DATABASE_URL}
    # or:
    # env_file:
    #   - ../apps/api/.env.production
    # secrets:
    #   - db_url
🧹 Nitpick comments (9)
apps/frontend/package.json (2)

21-21: Pin @expo/server or document update policy.

Caret range can pull breaking changes. Consider exact pin or Renovate rule to avoid surprise deploys.

-"@expo/server": "^0.6.3"
+"@expo/server": "0.6.3"

12-13: Pin Bun version in Dockerfile and verify required build flags
Replace FROM oven/bun:latest in apps/frontend/Dockerfile with a specific Bun release (e.g. oven/bun:v1.x.y) to ensure reproducible builds, and confirm that the pinned version supports --compile, --minify-whitespace, --minify-syntax and --target bun.

apps/api/Dockerfile (1)

37-44: Avoid passing build flags through a package script unless guaranteed.

bun run --filter @universal/api build --compile ... assumes the 'build' script forwards unknown args. Consider inlining bun build here or assert the script does forward.

-RUN bun run --filter @universal/api build \
-	--compile \
-	--minify-whitespace \
-	--minify-syntax \
-	--target bun \
-	--outfile server \
-	./src/index.ts
+# Directly invoke bun build to avoid script arg-forwarding pitfalls
+RUN bun build \
+  --compile \
+  --minify-whitespace \
+  --minify-syntax \
+  --target bun \
+  --outfile apps/api/server \
+  apps/api/src/index.ts
apps/frontend/src/containers/auth/sign-up.container.tsx (1)

13-21: Extract inline param type and narrow 'field' to known keys.

Inline types hurt reuse; also allow only valid form keys to prevent typos at call sites.

-  const handleFormDataChange = ({
-    field,
-    value,
-  }: {
-    field: string;
-    value: string;
-  }) => {
+  type FormField = keyof typeof formData;
+  type UpdateFormDataPayload = { field: FormField; value: string };
+  const handleFormDataChange = ({ field, value }: UpdateFormDataPayload) => {
     signUpModel$.updateFormData({ field, value });
   };
apps/frontend/server.ts (3)

10-23: Serve static assets with caching and correct content types; also de-duplicate route logic.

Adds long-lived cache headers for hashed asset paths and reduces duplication.

Apply this diff:

-    "/_expo/static/*": async (request) => {
-      const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
-      const exists = await file.exists();
-      return exists
-        ? new Response(file)
-        : new Response("Not found", { status: 404 });
-    },
-    "/assets/*": async (request) => {
-      const file = Bun.file(`./dist/client/${new URL(request.url).pathname}`);
-      const exists = await file.exists();
-      return exists
-        ? new Response(file)
-        : new Response("Not found", { status: 404 });
-    },
+    "/_expo/static/*": async (request) => {
+      const pathname = new URL(request.url).pathname;
+      const file = Bun.file(`./dist/client/${pathname}`);
+      const exists = await file.exists();
+      return exists
+        ? new Response(file, {
+            headers: {
+              // Bun infers content-type in many cases, but being explicit avoids edge cases.
+              "Content-Type": file.type || "application/octet-stream",
+              "Cache-Control": "public, max-age=31536000, immutable",
+            },
+          })
+        : new Response("Not found", { status: 404 });
+    },
+    "/assets/*": async (request) => {
+      const pathname = new URL(request.url).pathname;
+      const file = Bun.file(`./dist/client/${pathname}`);
+      const exists = await file.exists();
+      return exists
+        ? new Response(file, {
+            headers: {
+              "Content-Type": file.type || "application/octet-stream",
+              "Cache-Control": "public, max-age=31536000, immutable",
+            },
+          })
+        : new Response("Not found", { status: 404 });
+    },

7-9: Bind to PORT from env and 0.0.0.0 for container compatibility.

Prevents port conflicts and ensures the service is reachable behind Docker/Cloud.

-const server = Bun.serve({
-  port: 4000,
+const server = Bun.serve({
+  hostname: "0.0.0.0",
+  port: Number(Bun.env.PORT ?? 4000),

35-36: Avoid console in committed code; gate or remove the log.

Project guideline: “Don't use console or debugger in committed code.”

-// biome-ignore lint/suspicious/noConsole: logging the server URL
-console.log(`Server listening on ${server.url}`);
+if (Bun.env.NODE_ENV !== "test") {
+  // eslint-disable-next-line no-console
+  console.log(`Server listening on ${server.url}`);
+}
docker/docker-compose.prod.yml (1)

18-25: Consider healthchecks and startup ordering for SSR/API dependencies.

If SSR calls the API during request handling, add depends_on with health conditions to avoid boot-time 5xx.

   frontend:
     build:
       context: ..
       dockerfile: ./apps/frontend/Dockerfile
     ports:
       - "4000:4000"
     env_file:
-      - ../apps/frontend/.env
+      - ../apps/frontend/.env
+    depends_on:
+      api:
+        condition: service_healthy

Ensure api defines a healthcheck.

docker/docker-compose.dev.yml (1)

43-46: Dev container may not hot-reload frontend code.

You mount source, but the container runs a compiled binary from the image. Without a watch/build step, code changes won’t reflect.

Options:

  • Switch CMD in dev to run Bun in watch mode and serve from source (different Dockerfile target).
  • Or add a bind for dist and trigger a build step on file changes.

Would you like a minimal dev-target Dockerfile and compose override?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 75d7b76 and f7863c3.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • apps/api/Dockerfile (1 hunks)
  • apps/frontend/.env (1 hunks)
  • apps/frontend/.env.example (1 hunks)
  • apps/frontend/Dockerfile (1 hunks)
  • apps/frontend/package.json (1 hunks)
  • apps/frontend/server.ts (1 hunks)
  • apps/frontend/src/containers/auth/sign-up.container.tsx (1 hunks)
  • docker/docker-compose.dev.yml (1 hunks)
  • docker/docker-compose.prod.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use object destructuring for method parameters (wrap multiple parameters in an object; define shapes with TypeScript interfaces)
Use authClient.useSession() for session state
Avoid React hooks; use Legend State observables directly (exception: Better Auth hooks)
Import UI components via path aliases (e.g., @/src/components/ui/)
Prefer union string/number literals over TypeScript enums
Use export type and import type for types
Don't use non-null assertions (!) or implicit any
Avoid TypeScript namespaces; prefer ESM modules

Files:

  • apps/frontend/src/containers/auth/sign-up.container.tsx
  • apps/frontend/server.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Platform.OS and process.env.EXPO_OS for conditional platform logic

Files:

  • apps/frontend/src/containers/auth/sign-up.container.tsx
  • apps/frontend/server.ts
**/*.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.tsx: Use React Native primitives (View, Text, etc.) instead of HTML elements
Use Tailwind responsive prefixes (sm:, md:, lg:) and NativeWind responsive utilities
Use web: prefix for web-only styles; use conditional rendering for native-only components
Use IconSymbol to map SF Symbols on iOS to Material Icons on other platforms
Web a11y: ensure buttons have type, anchors are valid, SVGs/iframes have title, html has lang, inputs have valid autocomplete, ARIA roles/props/values are valid

Files:

  • apps/frontend/src/containers/auth/sign-up.container.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx}: Don't use the arguments object
Use for...of instead of Array.forEach
Use arrow functions instead of function expressions
Use Date.now() instead of new Date().getTime()
Use .flatMap() instead of map().flat() when possible
Prefer regex literals over RegExp constructor when possible
Use concise optional chaining instead of chained logical expressions
Always provide radix to parseInt()
Don't use var; use const/let and prefer const when single assignment
Use template literals over string concatenation
Use strict equality (===/!==)
Don't use console or debugger in committed code
Prevent import cycles and avoid namespace imports
Don't hardcode secrets (API keys/tokens) in source
Don't have unused vars, params, imports, labels, or private members
Avoid await inside loops; ensure Promise-like statements are handled
Disallow bitwise operators and comma operator
Ensure switch statements are exhaustive; default clause comes last; no fallthrough or duplicate cases
Use object spread over Object.assign when creating new objects
Prefer Array.isArray over instanceof Array; use at() instead of integer index access where appropriate
No unreachable code, duplicate conditions, or assignments in conditionals
Always use radix with parseInt; prefer Number.isNaN/Number.isFinite over globals
Disallow using global eval, with, var, and unsafe negation
Ensure typeof comparisons use valid strings; getters return values; constructors don’t return values

Files:

  • apps/frontend/src/containers/auth/sign-up.container.tsx
  • apps/frontend/server.ts
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{tsx,jsx}: Provide key props in lists; avoid array index as key
Don't define React components inside other components
Don't use event handlers on non-interactive elements; make interactive roles focusable
Don't use both children and dangerouslySetInnerHTML; avoid dangerous JSX props
Use <>...</> instead of ...

Files:

  • apps/frontend/src/containers/auth/sign-up.container.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-03T13:07:10.615Z
Learnt from: CR
PR: fyndx/universal-starter#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-03T13:07:10.615Z
Learning: Applies to src/containers/**/*.container.tsx : Create containers in src/containers/ mirroring Expo Router paths (e.g., admin/add-user.container.tsx)

Applied to files:

  • apps/frontend/src/containers/auth/sign-up.container.tsx
🧬 Code graph analysis (1)
apps/frontend/src/containers/auth/sign-up.container.tsx (1)
apps/frontend/src/app/(public)/auth/sign-in.tsx (3)
  • SignIn (21-180)
  • ctx (41-52)
  • email (69-95)
🔇 Additional comments (4)
apps/api/Dockerfile (1)

45-57: Confirm CA certificates and timezone data in all distroless/base Docker images
Distroless/base omits CA bundles and tzdata by default. For apps/api/Dockerfile (line 45), apps/workers/Dockerfile (line 33), and apps/frontend/Dockerfile (line 26), verify the presence of /etc/ssl/certs and timezone data—or switch to gcr.io/distroless/base-debian (which includes certs and tzdata) or layer them in manually—to ensure Prisma/HTTP TLS and correct timezone handling.

apps/frontend/src/containers/auth/sign-up.container.tsx (2)

9-11: State access via Legend observables looks good.


43-58: UI flow/render conditions unchanged — LGTM.

Prop order/formatting only; behavior consistent.

docker/docker-compose.dev.yml (1)

34-41: Env wiring looks correct for dev.

Pathing matches the repository layout and aligns with the prod fix above.

Comment thread apps/frontend/.env
@@ -1,2 +1,2 @@
EXPO_PUBLIC_API_URL=https://universal-starter.onrender.com
EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Prod compose will point frontend to staging API — config mismatch.

If docker-compose.prod.yml loads this file, the public app will call the staging API. Split envs and reference the right one per compose file.

Run to confirm compose env_file linkage:

Recommended changes:

-EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io
+EXPO_PUBLIC_API_URL=http://localhost:3000

And create per-env files:

+apps/frontend/.env.development
+EXPO_PUBLIC_API_URL=http://localhost:3000
+
+apps/frontend/.env.production
+EXPO_PUBLIC_API_URL=https://api.yourdomain.com

Then in compose:

  • dev: env_file: ../apps/frontend/.env.development
  • prod: env_file: ../apps/frontend/.env.production

🏁 Script executed:

#!/bin/bash
# Verify which env files prod/dev compose reference
rg -nP 'env_file\s*:\s*(.+)$' -C2 docker/docker-compose.*.yml

Length of output: 96


🏁 Script executed:

#!/bin/bash
# Search for any .env references in compose files
rg -nP '\.env' -n -C2 docker/docker-compose.*.yml
# Search for explicit EXPO_PUBLIC_API_URL usage in compose files
rg -nP 'EXPO_PUBLIC_API_URL' -n -C2 docker/docker-compose.*.yml

Length of output: 1701


Split frontend .env for production vs. development
Both docker-compose.dev.yml and docker-compose.prod.yml load the same apps/frontend/.env, which currently points at the staging API (EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io), so production will call staging.

  • Create apps/frontend/.env.development with EXPO_PUBLIC_API_URL=http://localhost:3000
  • Create apps/frontend/.env.production with EXPO_PUBLIC_API_URL=https://api.yourdomain.com
  • Update docker-compose.dev.yml to env_file: ../apps/frontend/.env.development
  • Update docker-compose.prod.yml to env_file: ./apps/frontend/.env.production
🤖 Prompt for AI Agents
In apps/frontend/.env around line 1, the current single .env points to the
staging API causing production to call staging; create two environment files
apps/frontend/.env.development (set EXPO_PUBLIC_API_URL=http://localhost:3000)
and apps/frontend/.env.production (set
EXPO_PUBLIC_API_URL=https://api.yourdomain.com), then update
docker-compose.dev.yml to reference env_file: ../apps/frontend/.env.development
and docker-compose.prod.yml to reference env_file:
./apps/frontend/.env.production so dev and prod load the correct environment
variables.

@@ -1,2 +1,2 @@
EXPO_PUBLIC_API_URL=https://universal-starter.onrender.com
EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don't default examples to a staging endpoint; use placeholders per env.

Hardcoding a staging URL in the example can leak intent and get copied to prod. Prefer a neutral default with comments for env-specific overrides.

-EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io
+EXPO_PUBLIC_API_URL=http://localhost:3000
+# For production: https://api.yourdomain.com
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EXPO_PUBLIC_API_URL=https://staging-universal-api.coolify.fyndx.io
EXPO_PUBLIC_API_URL=http://localhost:3000
# For production: https://api.yourdomain.com
🤖 Prompt for AI Agents
In apps/frontend/.env.example around lines 1 to 1, the example currently
hardcodes a staging API URL; replace this with a neutral placeholder and a short
comment indicating environment-specific overrides. Change the value to something
like EXPO_PUBLIC_API_URL=https://your-api.example.com (or
EXPO_PUBLIC_API_URL=${API_URL}) and add a one-line comment above explaining to
set the real endpoint per environment (development/staging/production) rather
than copying a staging URL.

Comment thread apps/frontend/Dockerfile Outdated
Comment thread apps/frontend/Dockerfile Outdated
Comment on lines +26 to +37
FROM gcr.io/distroless/base

WORKDIR /app

COPY --from=build /app/apps/frontend/dist ./dist
COPY --from=build /app/apps/frontend/server ./server

ENV NODE_ENV=production

CMD [ "./server" ]

EXPOSE 4000 No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Harden the final image: run as nonroot and ensure certs are present.

Distroless defaults to root. Run as nonroot and pre-own the files. CA certs are typically present in distroless base, but if SSR makes outbound HTTPS calls, verify at runtime.

 FROM gcr.io/distroless/base
 
 WORKDIR /app
 
-COPY --from=build /app/apps/frontend/dist ./dist
-COPY --from=build /app/apps/frontend/server ./server
+COPY --from=build /app/apps/frontend/dist ./dist
+COPY --from=build /app/apps/frontend/server ./server
+USER nonroot:nonroot
 
 ENV NODE_ENV=production
 
 CMD [ "./server" ]
 
 EXPOSE 4000

After building, verify HTTPS works from the container (e.g., fetch https://example.com from SSR) to confirm CA certs availability.


Enforce non-root execution and correct file ownership

  • Switch to the non-root variant (or add USER nonroot:nonroot) and ensure /app content is owned by that user, for example:
    FROM gcr.io/distroless/base:nonroot
    WORKDIR /app
    COPY --from=build --chown=nonroot:nonroot /app/apps/frontend/dist ./dist
    COPY --from=build --chown=nonroot:nonroot /app/apps/frontend/server ./server
    ENV NODE_ENV=production
    CMD ["./server"]
    EXPOSE 4000
    (stackoverflow.com)
  • The base image already includes CA certificates; verify outbound HTTPS (e.g. fetch https://example.com) from the container at runtime. (github.com)

Comment thread apps/frontend/server.ts
import { createRequestHandler } from "@expo/server";
import Bun from "bun";

const handler = createRequestHandler(path.join(import.meta.dir, "dist/server"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

SSR handler path will break inside the container (import.meta.dir points to build-time path). Use a cwd-based path.

At runtime, the compiled binary runs from /app and dist is copied to /app/dist. Using import.meta.dir risks resolving to /app/apps/frontend (or a build-time path) that doesn’t exist in the final image.

Apply this diff:

-const handler = createRequestHandler(path.join(import.meta.dir, "dist/server"));
+const handler = createRequestHandler(path.resolve("dist/server"));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handler = createRequestHandler(path.join(import.meta.dir, "dist/server"));
const handler = createRequestHandler(path.resolve("dist/server"));
🤖 Prompt for AI Agents
In apps/frontend/server.ts around line 5, the SSR handler currently uses
import.meta.dir which resolves a build-time path and will break inside the
container; change it to resolve the runtime working directory (e.g., use
process.cwd() or path.resolve(process.cwd(), 'dist', 'server')) so the handler
points to /app/dist/server at runtime; update the createRequestHandler call to
join/resolve against process.cwd() instead of import.meta.dir.

Comment thread apps/frontend/server.ts
Comment on lines +24 to +31
"/*": async (request) => await handler(request),
"/canvaskit.wasm": async (_request) => {
const file = Bun.file("./dist/client/canvaskit.wasm");
const exists = await file.exists();
return exists
? new Response(file)
: new Response("Not found here", { status: 404 });
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wildcard route likely shadows /canvaskit.wasm. Put the wasm route before "/*".

With a catch-all "/*" in routing, the /canvaskit.wasm handler may never be reached.

Apply this diff to reorder:

-    "/*": async (request) => await handler(request),
-    "/canvaskit.wasm": async (_request) => {
+    "/canvaskit.wasm": async (_request) => {
       const file = Bun.file("./dist/client/canvaskit.wasm");
       const exists = await file.exists();
       return exists
-        ? new Response(file)
+        ? new Response(file, {
+            headers: {
+              "Content-Type": "application/wasm",
+              "Cache-Control": "public, max-age=31536000, immutable",
+            },
+          })
         : new Response("Not found here", { status: 404 });
     },
+    "/*": async (request) => await handler(request),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"/*": async (request) => await handler(request),
"/canvaskit.wasm": async (_request) => {
const file = Bun.file("./dist/client/canvaskit.wasm");
const exists = await file.exists();
return exists
? new Response(file)
: new Response("Not found here", { status: 404 });
},
"/canvaskit.wasm": async (_request) => {
const file = Bun.file("./dist/client/canvaskit.wasm");
const exists = await file.exists();
return exists
? new Response(file, {
headers: {
"Content-Type": "application/wasm",
"Cache-Control": "public, max-age=31536000, immutable",
},
})
: new Response("Not found here", { status: 404 });
},
"/*": async (request) => await handler(request),
🤖 Prompt for AI Agents
In apps/frontend/server.ts around lines 24 to 31, the catch-all "/*" route
currently appears before the specific "/canvaskit.wasm" route causing the wasm
handler to be shadowed; reorder the routes so the "/canvaskit.wasm" entry comes
before the "/*" entry (move the /canvaskit.wasm block above the wildcard) so the
specific path is matched first and the catch-all remains last.

Comment on lines +24 to +25
env_file:
- ./apps/frontend/.env

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Broken env_file path; should be relative to the compose file directory.

dev uses ../apps/frontend/.env; prod should match. ./apps/frontend/.env will resolve to docker/apps/frontend/.env and fail.

-    env_file:
-      - ./apps/frontend/.env
+    env_file:
+      - ../apps/frontend/.env
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env_file:
- ./apps/frontend/.env
env_file:
- ../apps/frontend/.env
🤖 Prompt for AI Agents
In docker/docker-compose.prod.yml around lines 24 to 25, the env_file path is
incorrect (./apps/frontend/.env) because env_file is resolved relative to the
compose file directory; change it to ../apps/frontend/.env to match dev and
ensure the .env file is found (update any other env_file entries similarly if
they use ./ instead of ../).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
.github/workflows/docker-build.yml (5)

83-83: Tag format change removes hyphen — verify downstream expectations.
Switching from branch- to branch can break scripts or dashboards that parse tag patterns and reduces readability.

Apply to restore the hyphen if not intentional:

-            type=sha,prefix={{branch}}
+            type=sha,prefix={{branch}}-

122-122: Workers image tags: keep hyphen for consistency/back-compat.
Mirror the API decision to avoid mixed formats across services.

-            type=sha,prefix={{branch}}
+            type=sha,prefix={{branch}}-

134-171: New build-frontend job — solid. Two quick improvements: fork-safety and build cache.

  • Push on forked PRs will fail due to lack of package write perms. Gate push/login accordingly.
  • Add BuildKit cache to speed rebuilds.
       - name: Log in to Container Registry
-        uses: docker/login-action@v3
+        if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
+        uses: docker/login-action@v3
         with:
           registry: ${{ env.REGISTRY }}
           username: ${{ github.actor }}
           password: ${{ secrets.GITHUB_TOKEN }}

       - name: Extract metadata
         id: meta
         uses: docker/metadata-action@v5
         with:
           images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend
           tags: |
             type=ref,event=branch
             type=ref,event=pr
-            type=sha,prefix={{branch}}
+            type=sha,prefix={{branch}}-
             type=raw,value=latest,enable={{is_default_branch}}

       - name: Build and push Frontend image
         uses: docker/build-push-action@v5
         with:
           context: .
           file: ./apps/frontend/Dockerfile
-          push: true
+          push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
           tags: ${{ steps.meta.outputs.tags }}
           labels: ${{ steps.meta.outputs.labels }}

146-152: Optional: minimize noisy failures on forked PRs.
Adding the same fork-guard to API/Workers login steps will keep all jobs consistent.


153-163: Action versions: bump to latest stable releases
• docker/build-push-action → v6.15.0
• docker/metadata-action → v5.7.0
• docker/login-action → v3.3.0
• dorny/paths-filter → v3.0.2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7863c3 and 86e69aa.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • .github/workflows/docker-build.yml (7 hunks)
  • apps/frontend/Dockerfile (1 hunks)
  • apps/frontend/package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/frontend/Dockerfile
  • apps/frontend/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-frontend
🔇 Additional comments (4)
.github/workflows/docker-build.yml (4)

10-10: Triggers: frontend included on push — looks good.
Keeps CI scoped and avoids unnecessary builds.


19-19: Triggers: frontend included on PRs — good coverage.
Consistent with push trigger.


33-33: paths-filter output wired for frontend — LGTM.
Enables clean job gating.


51-55: Frontend filter definition — sensible paths.
Including packages/** is appropriate for a shared monorepo.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/frontend/Dockerfile (1)

26-35: Harden runtime: run as non-root and pre-own files in distroless

Repeat of prior note—still applicable. Switch to nonroot variant and set ownership to avoid permission issues and improve security posture.

-FROM gcr.io/distroless/base
+FROM gcr.io/distroless/base:nonroot
 
 WORKDIR /app
 
-COPY --from=build /app/apps/frontend/dist ./dist
-COPY --from=build /app/apps/frontend/server ./server
+COPY --from=build --chown=nonroot:nonroot /app/apps/frontend/dist ./dist
+COPY --from=build --chown=nonroot:nonroot /app/apps/frontend/server ./server
 
 ENV NODE_ENV=production
 
-CMD [ "./server" ]
+USER nonroot:nonroot
+CMD ["./server"]
🧹 Nitpick comments (2)
apps/frontend/Dockerfile (2)

3-6: Clean up unused ENV or wire it through

ENV ENVIRONMENT=${ENVIRONMENT} is unused; either drop it or set NODE_ENV from it for clarity.

-ARG ENVIRONMENT=production
-ENV ENVIRONMENT=${ENVIRONMENT}
-# ENV NODE_ENV=${ENVIRONMENT}
+ARG ENVIRONMENT=production
+ENV NODE_ENV=${ENVIRONMENT}

1-1: Pin builder and runtime images for reproducibility

oven/bun:latest and distroless :latest drift. Pin tags (and ideally digests) to prevent surprise breakages in CI.

Example:

-FROM oven/bun:latest AS base
+FROM oven/bun:1.1.XX-debian AS base
-...
-FROM gcr.io/distroless/base:nonroot
+FROM gcr.io/distroless/base:nonroot@sha256:<digest>

Also applies to: 26-26

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86e69aa and a22de8c.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • apps/frontend/Dockerfile (1 hunks)
  • apps/frontend/package.json (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/frontend/package.json
🧰 Additional context used
🪛 GitHub Actions: Docker Build
apps/frontend/Dockerfile

[error] 22-22: Command 'cd apps/frontend && bunx expo export --platform web' failed with exit code 7 during image build.

🔇 Additional comments (2)
apps/frontend/Dockerfile (2)

37-37: Ports match EXPOSE 4000
server.ts explicitly listens on port 4000, matching the Dockerfile.


9-9: No changes needed; bun.lock is the correct canonical Bun lockfile
The repository contains a bun.lock (text‐based lockfile introduced in Bun v1.1.39 and default since v1.2) and copying it in the Dockerfile is appropriate.

Likely an incorrect or invalid review comment.

Comment thread apps/frontend/Dockerfile Outdated
Comment thread apps/frontend/Dockerfile Outdated
Comment on lines +20 to +24
RUN bun install --ignore-scripts
# Create web build
RUN cd apps/frontend && bunx expo export --platform web
# Create server build
RUN cd apps/frontend && bun run expo:server

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Unblock failing build: don’t ignore postinstalls and avoid bunx for Expo; ensure Node is available

The pipeline fails at Line 22. Likely causes: (a) --ignore-scripts skips needed postinstalls, (b) Expo CLI expects Node (bunx can be incompatible). Install Node, run full install, and invoke the package script.

 WORKDIR /app
 
-RUN bun install --ignore-scripts
-# Create web build
-RUN cd apps/frontend && bunx expo export --platform web
-# Create server build
-RUN cd apps/frontend && bun run expo:server
+RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/*
+RUN bun install --frozen-lockfile
+# Create web build (use workspace script so it resolves local config/deps)
+RUN cd apps/frontend && bun run export:web
+# Create server build
+RUN cd apps/frontend && bun run expo:server
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN bun install --ignore-scripts
# Create web build
RUN cd apps/frontend && bunx expo export --platform web
# Create server build
RUN cd apps/frontend && bun run expo:server
WORKDIR /app
RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/*
RUN bun install --frozen-lockfile
# Create web build (use workspace script so it resolves local config/deps)
RUN cd apps/frontend && bun run export:web
# Create server build
RUN cd apps/frontend && bun run expo:server
🧰 Tools
🪛 GitHub Actions: Docker Build

[error] 22-22: Command 'cd apps/frontend && bunx expo export --platform web' failed with exit code 7 during image build.

🤖 Prompt for AI Agents
In apps/frontend/Dockerfile around lines 20-24, the build is failing because
--ignore-scripts prevents required postinstall steps and bunx can be
incompatible with Expo (Node not ensured); remove the --ignore-scripts flag so
postinstall scripts run, ensure a Node runtime is installed in the image before
running installs (install nodejs/node and npm or nvm in the Dockerfile), and
replace bunx expo invocations with the project's package script executed via the
package manager (e.g., run the repository's expo export/server scripts with bun
run or npm run from apps/frontend) so Expo runs under a proper Node environment.

@qodo-code-review

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: build-frontend

Failed stage: Build Web [❌]

Failed test name: ""

Failure summary:

The action failed during the step "cd apps/frontend && bun run export:web" because the Expo web
export requires a missing dependency.
- Expo detected web usage but @expo/metro-runtime@~5.0.4 is
not installed.
- The command suggested installing it via npx expo install @expo/metro-runtime.
-
Alternatively, if web is not intended, remove "web" from the platforms array in the Expo config.
-
The script export:web exited with code 1, causing the workflow to fail.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

136:  + @types/bun@1.2.21
137:  + lefthook@1.12.3
138:  + typescript@5.9.2
139:  + ultracite@5.3.3
140:  2588 packages installed [13.13s]
141:  ##[group]Run cd apps/frontend && bun run export:web
142:  �[36;1mcd apps/frontend && bun run export:web�[0m
143:  shell: /usr/bin/bash -e {0}
144:  env:
145:  REGISTRY: ghcr.io
146:  IMAGE_NAME: fyndx/universal-starter
147:  ##[endgroup]
148:  $ bunx expo export --platform web
149:  env: load .env.production .env
150:  env: export EXPO_PUBLIC_API_URL EXPO_PUBLIC_STORAGE_PREFIX
151:  CommandError: It looks like you're trying to use web support but don't have the required
152:  dependencies installed.
153:  Install @expo/metro-runtime@~5.0.4 by running:
154:  npx expo install @expo/metro-runtime
155:  If you're not using web, please ensure you remove the "web" string from the
156:  platforms array in the project Expo config.
157:  error: script "export:web" exited with code 1
158:  ##[error]Process completed with exit code 1.
159:  Post job cleanup.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
apps/frontend/Dockerfile (2)

19-22: Build step likely fails: run the script via bun run (and avoid bunx).

Use the workspace script so Expo runs under your project config. bunx expo … is brittle here and was the previous failure point.

-RUN cd apps/frontend && \
-    bunx expo export --platform web
+RUN cd apps/frontend && \
+    bun run export:web
 RUN cd apps/frontend && \
     bun run export:server

25-35: Harden the runtime image: run as non-root and ensure ownership.

Distroless defaults to root. Switch to the nonroot variant and copy with ownership to avoid permission surprises at runtime.

-FROM gcr.io/distroless/base
+FROM gcr.io/distroless/base:nonroot
 
 WORKDIR /app
 
-COPY --from=base ./apps/frontend/dist .
-COPY --from=base ./apps/frontend/server .
+COPY --from=base --chown=nonroot:nonroot /app/apps/frontend/dist ./dist
+COPY --from=base --chown=nonroot:nonroot /app/apps/frontend/server ./server
 
 ENV NODE_ENV=production
+USER nonroot:nonroot
 
 CMD [ "./server" ]
 
 EXPOSE 4000
🧹 Nitpick comments (5)
apps/frontend/Dockerfile (2)

16-18: Avoid redundant per-package install.

Installing in the workspace root is sufficient; a second install in apps/frontend is unnecessary and can create drift.

-RUN cd apps/frontend && \
-    bun install 
+# Root install above covers all workspaces; no per-package install needed

5-7: ENVIRONMENT only exists in the build stage.

If the binary reads ENV at runtime, propagate ENVIRONMENT in the final stage too.

 ARG ENVIRONMENT=production
 ENV ENVIRONMENT=${ENVIRONMENT}
 # ENV NODE_ENV=production
+...
+FROM gcr.io/distroless/base:nonroot
+ARG ENVIRONMENT=production
+ENV ENVIRONMENT=${ENVIRONMENT}
.github/workflows/docker-build.yml (3)

146-157: Trim redundant pre-build steps; let Docker build do the builds.

These steps rebuild web/server outside the image and add CI time without affecting the Docker build result. Prefer relying on the Dockerfile or move these into a separate “verify” job.

-      - name: Set up Bun
-        uses: oven-sh/setup-bun@v2
-
-      - name: Install dependencies
-        run: bun install
-
-      - name: Build Web
-        run: cd apps/frontend && bun run export:web
-
-      - name: Build Server
-        run: cd apps/frontend && bun run export:server
+      # Optional: keep a separate verify job if you want fast feedback outside Docker

169-175: Tag format change: confirm consumers expect no dash between branch and SHA.

You changed to type=sha,prefix={{branch}}. Ensure any tooling parsing tags doesn’t rely on the old hyphen.


176-183: Speed up Docker builds with BuildKit cache and pass build args.

Push/pull cache shortens monorepo builds; propagate ENVIRONMENT if you intend to vary builds.

       - name: Build and push Frontend image
         uses: docker/build-push-action@v5
         with:
           context: .
           file: ./apps/frontend/Dockerfile
           push: true
+          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:buildcache
+          cache-to: type=registry,mode=max,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:buildcache
+          build-args: |
+            ENVIRONMENT=production
           tags: ${{ steps.meta.outputs.tags }}
           labels: ${{ steps.meta.outputs.labels }}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a22de8c and 96f757e.

📒 Files selected for processing (4)
  • .github/workflows/docker-build.yml (7 hunks)
  • apps/frontend/.dockerignore (1 hunks)
  • apps/frontend/Dockerfile (1 hunks)
  • apps/frontend/package.json (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/frontend/.dockerignore
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/frontend/package.json
🔇 Additional comments (1)
.github/workflows/docker-build.yml (1)

10-10: LGTM: frontend path filters and outputs are wired correctly.

Also applies to: 19-19, 33-34, 51-55

Comment thread apps/frontend/Dockerfile
Comment on lines +11 to +15
COPY . .

# Install dependencies (this layer will be cached if package files don't change)
RUN bun install --ignore-scripts

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Fix caching and postinstall behavior: don’t copy the whole context before install; drop --ignore-scripts.

Copy only manifests first to leverage Docker layer cache, then run a strict install. --ignore-scripts will likely break Expo-related postinstalls.

 WORKDIR /app
-# Copy package files first for better caching
-COPY . .
-
-# Install dependencies (this layer will be cached if package files don't change)
-RUN bun install --ignore-scripts
+# Copy manifests for better caching
+COPY package.json bun.lockb ./
+COPY packages/*/package.json packages/*/
+COPY apps/*/package.json apps/*/
+# Install deps (fail if lock diverges)
+RUN bun install --frozen-lockfile
+
+# Now bring in the rest
+COPY . .
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
COPY . .
# Install dependencies (this layer will be cached if package files don't change)
RUN bun install --ignore-scripts
WORKDIR /app
# Copy manifests for better caching
COPY package.json bun.lockb ./
COPY packages/*/package.json packages/*/
COPY apps/*/package.json apps/*/
# Install deps (fail if lock diverges)
RUN bun install --frozen-lockfile
# Now bring in the rest of the application
COPY . .
🤖 Prompt for AI Agents
In apps/frontend/Dockerfile around lines 11 to 15, the Dockerfile currently
copies the entire build context before running bun install and uses
--ignore-scripts; change this to copy only dependency manifests (e.g.,
package.json, package-lock.json, bun.lockb, yarn.lock, and any Expo-specific
config files) into the image, run bun install without --ignore-scripts, then
copy the rest of the repo. Remove the --ignore-scripts flag so postinstall
scripts run (needed for Expo), and ensure manifests are copied first to get
proper layer caching before the full COPY . .

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96f757e and b3a52f7.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • apps/frontend/app.json (1 hunks)
  • apps/frontend/package.json (5 hunks)
🔇 Additional comments (3)
apps/frontend/package.json (3)

12-14: Remove build artifact name clarity nit. The export:server script produces a binary named server, which exactly matches both the COPY --from=base ./apps/frontend/server . and CMD ["./server"] in apps/frontend/Dockerfile.

Likely an incorrect or invalid review comment.


2-2: Ensure complete renaming of @universal/app to @universal/super-app
I didn’t find any remaining @universal/app references in your package.json files, but please search all source (e.g. rg -nP "['\]@universal/app['`]"`) and update any import/require statements, Docker contexts, CI configs or workspace entries still pointing to the old name.


21-21: Confirmed @expo/server version compatibility: ^0.6.3 aligns with Expo SDK 53 and expo-router 5.1.x; no changes needed.

Comment thread apps/frontend/app.json
Comment on lines +41 to 43
"expo-video",
"expo-secure-store"
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

SecureStore plugin: verify web behavior and usage guards.

Adding "expo-secure-store" is fine for native. On web builds it can be unsecure or limited; ensure calls are guarded (e.g., Platform.OS !== 'web') or handled with a fallback.

If you’re targeting web (per web.output=server), confirm SDK 53’s web support/limitations for expo-secure-store and whether additional configuration is required:


🌐 Web query:

Does Expo SDK 53 support expo-secure-store on Web, and what are its limitations or recommended fallbacks?

💡 Result:

Short answer: No — in Expo SDK 53 expo-secure-store does not support web. The SDK 53 docs state isAvailableAsync resolves true on Android and iOS only (web is not supported). [1]

Key limitations to note:

  • No web implementation in SDK 53 (mobile-only). [1]
  • Stored value size limit ≈ 2048 bytes. [2]
  • requireAuthentication has platform caveats (not supported in Expo Go when FaceID/biometrics need extra plist key). [2]
  • Data is not guaranteed to survive app uninstall; keys tied to biometrics can be invalidated if biometrics change. [1][2]

Recommended fallbacks on web:

  • Use a web storage solution (localStorage, sessionStorage, or IndexedDB) or @react-native-async-storage/async-storage when running on web, optionally combined with a JS-level encryption layer if you need confidentiality. Many users switch to AsyncStorage/localStorage on Platform.OS === 'web' and use SecureStore only on mobile. [3][4]

Sources:

  • Expo SDK 53 SecureStore docs. [1]
  • Expo SecureStore general docs (size limit, platform notes). [2]
  • Community example recommending AsyncStorage fallback for web. [3]

References:
[1] Expo SDK 53 SecureStore docs — docs.expo.dev/versions/v53.0.0/sdk/securestore/
[2] Expo SecureStore (general) — docs.expo.dev/sdk/securestore/
[3] StackOverflow: use AsyncStorage on web when SecureStore not available.


Guard SecureStore on Web and Implement Fallback
expo-secure-store isn’t supported on Web in Expo SDK 53; wrap all SecureStore calls in Platform.OS !== 'web' and fallback to AsyncStorage/localStorage (optionally with JS-level encryption) when running on Web.

Comment on lines +12 to 14
"export:web": "bunx expo export --platform web",
"export:server": "bun build --compile --minify-whitespace --minify-syntax --target bun --outfile server ./server.ts",
"lint": "expo lint",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Bun scripts added: pin Bun and ensure postinstall compatibility.

Since you rely on bun/bunx, declare the runtime to avoid accidental npm/yarn environments; also revisit postinstall which currently uses npx and can fail under Bun-only builds.

Add an engines/packageManager hint (example outside this hunk):

{
  "engines": { "bun": ">=1.1.20" },
  "packageManager": "bun@1.1.20"
}

Consider switching postinstall to bunx (or make it conditional):

-    "postinstall": "npx setup-skia-web"
+    "postinstall": "bunx setup-skia-web"
🤖 Prompt for AI Agents
In apps/frontend/package.json around lines 12 to 14, the project uses bun/bunx
scripts but does not declare Bun as the runtime or pin the packageManager, and
the postinstall still uses npx which can fail in Bun-only environments; add an
"engines" entry (e.g. "bun": ">=1.1.20") and a "packageManager" field (e.g.
"bun@1.1.20") to the package.json root, and update the postinstall script to use
bunx (or make it conditional: detect bun and fall back to npx/yarn/npm) so
postinstall works reliably under Bun while keeping compatibility with other
environments.

"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"expo": "53.0.20",
"expo": "53.0.22",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

SDK patch bumps: confirm alignment across Expo ecosystem.

Upgrading expo and related packages is good; ensure all versions match the supported set for SDK 53 (React/React Native, Expo modules).

If you can, run expo-doctor locally. Otherwise, confirm via docs:

Also applies to: 58-61, 64-64


🌐 Web query:

For Expo SDK 53, what are the recommended versions of react, react-native, expo-router, expo-system-ui, and expo-secure-store?

💡 Result:

Recommended versions for Expo SDK 53

  • react: 19.0.0. [1][2]
  • react-native: 0.79 (0.79.x). [1][2]
  • expo-router: 5.x (use the 5.1.x series / sdk-52+ 5.x releases). [3]
  • expo-system-ui: ~5.0.x (5.0.* series). [4]
  • expo-secure-store: ~14.2.3 (bundled ~14.2.3 for SDK 53). [5]

Sources: Expo SDK version matrix & SDK 53 release notes; expo-router npm; expo-system-ui npm; expo-secure-store docs.
[1] https://docs.expo.dev/versions/latest/ — SDK/React/React Native mapping.
[2] https://expo.dev/changelog/sdk-53 — SDK 53 release notes.
[3] https://www.npmjs.com/package/expo-router — expo-router versions/tags.
[4] https://www.npmjs.com/package/expo-system-ui — expo-system-ui versions.
[5] https://docs.expo.dev/versions/latest/sdk/securestore — bundled version info.


Align peer dependency versions with Expo SDK 53
In apps/frontend/package.json, update dependencies to the SDK-53–bundled versions:
• react → 19.0.0 (docs.expo.dev)
• react-native → 0.79 (docs.expo.dev)
• expo-router → ^5.1.4 (npmjs.com)
• expo-system-ui → ~5.0.11 (docs.expo.dev)
• expo-secure-store → ~14.2.3 (docs.expo.dev)

Ensure all other expo-* modules use versions bundled with SDK 53.

🤖 Prompt for AI Agents
In apps/frontend/package.json around line 51, update dependency versions to
match Expo SDK 53: set "react" to "19.0.0", "react-native" to "0.79",
"expo-router" to "^5.1.4", "expo-system-ui" to "~5.0.11", and
"expo-secure-store" to "~14.2.3"; also audit and align all other "expo-*" module
versions to the SDK‑53 bundled versions per the Expo 53 docs so the package.json
reflects the exact SDK‑53 compatible dependency set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant