server { listen %%interface%%:%%port%% default_server; include /etc/nginx/includes/server_params.conf; include /etc/nginx/includes/proxy_params.conf; proxy_buffering off; gzip_static off; client_max_body_size 0; location ^~ / { set $app '%%ingress_entry%%'; # Forward the request URI with the path byte-for-byte as received, and # the query string re-encoded so the characters Supervisor's ingress # proxy passes through bare (a space as "+", plus ":/@!$'()*,") do not # trip Seerr's OpenAPI validator. See njs/ingress.js for the details. proxy_pass http://127.0.0.1:5055$ingress_uri; proxy_http_version 1.1; proxy_set_header Referer $http_referer; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-Port $remote_port; proxy_set_header X-Forwarded-Host $host:$remote_port; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-Port $remote_port; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Accept-Encoding ""; proxy_hide_header X-Powered-By; proxy_read_timeout 90; add_header X-Frame-Options "SAMEORIGIN"; add_header Referrer-Policy "no-referrer"; absolute_redirect off; proxy_redirect ^ $app; proxy_redirect /setup $app/setup; proxy_redirect /login $app/login; sub_filter_once off; # Do not rewrite every response type blindly. text/html is implicit and # must not be listed - nginx pre-seeds it and warns "duplicate MIME type" # on every config load if it appears here as well. sub_filter_types application/javascript text/javascript application/json; # Seerr's "Discover" sidebar entry, the header logo and the 404 and # error pages are all . The server-rendered anchor has to # carry the ingress prefix *with* a trailing slash: Home Assistant routes # ingress on "/api/hassio_ingress/{token}/{path:.*}", so a slash-less # entry matches no route and Home Assistant answers its own plain-text # "404: Not Found" before the request ever reaches this add-on (#2975). sub_filter 'href="/"' 'href="$app/"'; sub_filter 'href="/login"' 'href="$app/login"'; # A matching rule for 'href:"/"' - the form those same links take once # compiled into the JS bundle - used to sit here. It is gone on purpose # and must not come back: it fed the ingress prefix into Next.js' own # route table, and next/link resolves a pushed href through # normalizePathTrailingSlash(), which drops a trailing slash while # `trailingSlash` is false (Seerr sets no override). Next therefore hard # navigated to the slash-less URL and recreated the same 404; on the root # page it instead threw "Invariant: attempted to hard navigate to the # same URL" and the click did nothing. That is the state PR #2976 left # #2975 in. Left alone the href stays "/", which removeTrailingSlash() # preserves, so the router matches its own "/" route and transitions # in-app - the path every other sidebar entry ("/requests", "/issues", # "/users", "/settings") already takes. Prefixing belongs in the rendered # anchor, never in the router's route table. # # Note that none of these rules are response-type scoped - sub_filter_types # includes JavaScript - so the anchor rule above avoids the bundle only # because the compiled output spells the prop 'href:"/"' and not # 'href="/"'. These are textual substitutions over someone else's minified # output: recheck them whenever Seerr or Next.js is upgraded. # "%%asset_tag%%" is a cache-busting marker carrying the add-on version, # substituted by 32-nginx_ingress.sh - which explains why it is needed. # In short: without it a browser replays the bundle this file produced at # the version it first loaded, for a year, and no later change to any # rule here can reach it. njs/ingress.js strips the marker back off # before proxying; the two belong together, do not change one alone. sub_filter '\/_next' '%%ingress_entry_escaped%%\/%%asset_tag%%\/_next'; sub_filter '/_next' '$app/%%asset_tag%%/_next'; sub_filter '/api/v1' '$app/api/v1'; sub_filter '/login/plex/loading' '$app/login/plex/loading'; sub_filter '/images/' '$app/images/'; sub_filter '/imageproxy/' '$app/imageproxy/'; sub_filter '/avatarproxy/' '$app/avatarproxy/'; sub_filter '/android-' '$app/android-'; sub_filter '/apple-' '$app/apple-'; sub_filter '/favicon' '$app/favicon'; sub_filter '/logo_' '$app/logo_'; sub_filter '/site.webmanifest' '$app/site.webmanifest'; } }