{"record":{"id":"3009fe907938e944","repo":"immich-app/immich","slug":"unable-to-open-resourcepaths-web-indexhtml-skipping-ssr-api","errorCode":null,"errorMessage":"Unable to open ${resourcePaths.web.indexHtml}, skipping SSR.","messagePattern":"Unable to open (.+?), skipping SSR\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/src/services/api.service.ts","lineNumber":46,"sourceCode":"@Injectable()\nexport class ApiService {\n  constructor(\n    private authService: AuthService,\n    private sharedLinkService: SharedLinkService,\n    private configRepository: ConfigRepository,\n    private logger: LoggingRepository,\n  ) {\n    this.logger.setContext(ApiService.name);\n  }\n\n  ssr(excludePaths: string[]) {\n    const { resourcePaths } = this.configRepository.getEnv();\n\n    let index = '';\n    try {\n      index = readFileSync(resourcePaths.web.indexHtml).toString();\n    } catch {\n      this.logger.warn(`Unable to open ${resourcePaths.web.indexHtml}, skipping SSR.`);\n    }\n\n    return async (request: Request, res: Response, next: NextFunction) => {\n      const method = request.method.toLowerCase();\n      if (\n        request.url.startsWith('/api') ||\n        (method !== 'get' && method !== 'head') ||\n        excludePaths.some((item) => request.url.startsWith(item))\n      ) {\n        return next();\n      }\n\n      const responseType = request.accepts('text/html');\n      if (!responseType) {\n        throw new NotAcceptableException(\n          `The route ${request.path} was requested as ${request.header('accept')}, but only returns text/html`,\n        );\n      }","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/server/src/services/api.service.ts#L28-L64","documentation":"The SSR middleware reads the frontend's index.html at startup to render pages server-side. If the file cannot be read, SSR is skipped and every page request falls back to the plain SPA served statically. The warning is logged once when ssr() builds the middleware.","triggerScenarios":"readFileSync(resourcePaths.web.indexHtml) throws when building the SSR middleware — index.html missing or unreadable at server startup.","commonSituations":"Server-only Docker image (immich-server without web build output); custom reverse-proxy/static hosting of the frontend; partial or interrupted deployment where the web assets directory is absent.","solutions":["Deploy the frontend build output (www/index.html) alongside the server if you want SSR.","If using the server-only image intentionally, ignore the warning — SPA mode still works.","Check IMMICH_ resource-path configuration so resourcePaths.web.indexHtml matches the actual build location.","Re-run/repair the deployment so the web assets are copied completely."],"exampleFix":"// before\nindex = readFileSync(resourcePaths.web.indexHtml).toString();\n// after\nif (existsSync(resourcePaths.web.indexHtml)) {\n  index = readFileSync(resourcePaths.web.indexHtml).toString();\n} else {\n  this.logger.warn(`SSR disabled: ${resourcePaths.web.indexHtml} not found`);\n}","handlingStrategy":"fallback","validationCode":"import { existsSync } from 'node:fs';\nconst ssrAvailable = existsSync(resourcePaths.web.indexHtml);","typeGuard":null,"tryCatchPattern":"let index = '';\ntry {\n  index = readFileSync(indexHtmlPath, 'utf8');\n} catch {\n  logger.warn(`SSR disabled: ${indexHtmlPath} missing; serving SPA`);\n}","preventionTips":["Deploy web build output together with the server when SSR is desired.","Keep resourcePaths config in sync with actual build layout.","Confirm assets copied fully in deployment scripts (check index.html exists post-deploy)."],"tags":["filesystem","ssr","configuration"],"backgroundTag":"file-not-found","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}