Skip to content

PL-292 Vite SSR#1360

Open
mikkojamG wants to merge 25 commits into
mainfrom
feat/PL-240-vite-ssr
Open

PL-292 Vite SSR#1360
mikkojamG wants to merge 25 commits into
mainfrom
feat/PL-240-vite-ssr

Conversation

@mikkojamG

@mikkojamG mikkojamG commented May 20, 2026

Copy link
Copy Markdown
Contributor

The current SSR implementation is really heavy. Migrated the application to use Vite Server-Side Rendering instead.

  • Replaces the dual vite build --watch + nodemon dev setup with a single server.mjs using Vite in middleware mode — giving full HMR with no manual rebuild step
  • Switches the client bundle from a monolithic IIFE to code-split ESM chunks with preload hints
  • Switches the server bundle from a fully-bundled CJS blob to ESM with externalized node_modules, improving cold start time
  • Consolidates CSS handling to Emotion only — removes the manual @mui/styles (JSS) ServerStyleSheets and isomorphic-style-loader extraction
  • Removes legacy browser/Node polyfills no longer needed on modern targets: core-js, regenerator-runtime, whatwg-fetch, intl, node-fetch
  • Adds a development Docker stage so docker compose can target it for local dev with HMR while the same Dockerfile still produces the production image

Azure: https://dev.azure.com/City-of-Helsinki/palvelukartta/_git/palvelukartta-pipelines/pullrequest/16188

Summary by CodeRabbit

Release Notes

  • New Features

    • Unified server architecture with improved request handling and per-request security policies.
    • Added rate limiting for enhanced stability.
    • Dynamic module loading for map features to improve compatibility.
  • Improvements

    • Streamlined build pipeline with separate client and server compilation.
    • Updated Material-UI theme integration across components.
    • Enhanced Docker configuration for flexible deployment.
  • Removals

    • Discontinued support for Internet Explorer.

@mikkojamG mikkojamG force-pushed the feat/PL-240-vite-ssr branch from 4ca45c4 to ba8142e Compare May 21, 2026 10:02
@mikkojamG mikkojamG force-pushed the feat/PL-240-vite-ssr branch from bdcfe53 to 139b483 Compare May 25, 2026 10:29
@mikkojamG mikkojamG force-pushed the feat/PL-240-pnpm branch 2 times, most recently from 6a9ff77 to d03127b Compare May 26, 2026 06:50
@mikkojamG mikkojamG force-pushed the feat/PL-240-vite-ssr branch from 64e4fa8 to 121b255 Compare May 26, 2026 09:01
@mikkojamG mikkojamG force-pushed the feat/PL-240-pnpm branch 5 times, most recently from 8fdf03c to 61dcade Compare June 1, 2026 05:25
Base automatically changed from feat/PL-240-pnpm to main June 1, 2026 05:41
@mikkojamG mikkojamG force-pushed the feat/PL-240-vite-ssr branch 2 times, most recently from 72666cb to f3f01e2 Compare June 1, 2026 11:48
Comment thread server.mjs Fixed
Comment thread server.mjs Fixed
@mikkojamG mikkojamG force-pushed the feat/PL-240-vite-ssr branch 2 times, most recently from fa1813a to 3d2a727 Compare June 1, 2026 11:51
@mikkojamG mikkojamG force-pushed the feat/PL-240-vite-ssr branch from 3d2a727 to 5e6bfb8 Compare June 9, 2026 12:25
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 772bf7a8-0267-4a9b-a523-76917ece5c73

📥 Commits

Reviewing files that changed from the base of the PR and between 90d70fd and f553349.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (52)
  • .babelrc
  • .eslintrc.json
  • Dockerfile
  • README.md
  • __mocks__/withStyles.js
  • client/client.js
  • compose.yml
  • config/index.js
  • config/package.json
  • index.html
  • package.json
  • pipelines/palvelukartta-ui-review.yml
  • scripts/fix-hds-core-cookie-consent.js
  • scripts/package.json
  • scripts/update-runtime-env.js
  • scripts/utils.js
  • server.mjs
  • server/ieMiddleware.js
  • server/server-entry.js
  • server/server.js
  • server/sitemapMiddlewares.js
  • server/utils.js
  • src/components/ListItems/SimpleListItem/SimpleListItem.js
  • src/components/ListItems/SuggestionItem/SuggestionItem.js
  • src/components/NewsInfo/components/NewsItem/NewsItem.js
  • src/components/SearchBar/SearchBarComponent.js
  • src/components/TabLists/TabLists.js
  • src/components/TopBar/DrawerMenu/DrawerMenu.js
  • src/createEmotionCache.js
  • src/entry-client.jsx
  • src/entry-server.jsx
  • src/layouts/EmbedLayout.js
  • src/redux/actions/services.js
  • src/serviceWorker.js
  • src/store.js
  • src/utils/unitHelper.js
  • src/views/AreaView/components/ServiceFilterContainer/ServiceFilterContainer.js
  • src/views/FeedbackView/FeedbackView.js
  • src/views/MapView/MapView.js
  • src/views/MapView/components/AddressMarker/AddressMarker.js
  • src/views/MapView/components/Districts/ParkingAreas.js
  • src/views/MapView/components/HideSidebarButton/HideSidebarButton.js
  • src/views/MapView/components/MarkerCluster/MarkerCluster.js
  • src/views/MapView/components/TransitStops/TransitStops.js
  • src/views/MapView/components/UserMarker/UserMarker.js
  • src/views/MapView/utils/createMap.js
  • src/views/MapView/utils/drawIcon.js
  • src/views/MapView/utils/mapActions.js
  • src/views/MapView/utils/swapCoordinates.js
  • src/views/MapView/utils/transitFetch.js
  • vite.config.js
  • vitest.config.js
💤 Files with no reviewable changes (7)
  • .babelrc
  • client/client.js
  • mocks/withStyles.js
  • server/utils.js
  • server/server.js
  • server/ieMiddleware.js
  • src/serviceWorker.js
👮 Files not reviewed due to content moderation or server errors (45)
  • compose.yml
  • index.html
  • src/utils/unitHelper.js
  • src/views/MapView/utils/drawIcon.js
  • src/components/ListItems/SuggestionItem/SuggestionItem.js
  • src/views/MapView/components/UserMarker/UserMarker.js
  • src/views/MapView/components/MarkerCluster/MarkerCluster.js
  • Dockerfile
  • config/package.json
  • scripts/fix-hds-core-cookie-consent.js
  • src/store.js
  • server.mjs
  • scripts/utils.js
  • vite.config.js
  • pipelines/palvelukartta-ui-review.yml
  • src/components/SearchBar/SearchBarComponent.js
  • .eslintrc.json
  • README.md
  • config/index.js
  • server/sitemapMiddlewares.js
  • src/components/TabLists/TabLists.js
  • src/views/MapView/MapView.js
  • src/views/MapView/utils/createMap.js
  • src/views/MapView/utils/mapActions.js
  • src/views/MapView/utils/swapCoordinates.js
  • src/entry-server.jsx
  • src/createEmotionCache.js
  • vitest.config.js
  • scripts/package.json
  • src/components/NewsInfo/components/NewsItem/NewsItem.js
  • src/views/MapView/components/Districts/ParkingAreas.js
  • src/views/MapView/components/HideSidebarButton/HideSidebarButton.js
  • src/views/FeedbackView/FeedbackView.js
  • src/components/TopBar/DrawerMenu/DrawerMenu.js
  • src/views/MapView/utils/transitFetch.js
  • src/views/MapView/components/TransitStops/TransitStops.js
  • src/views/MapView/components/AddressMarker/AddressMarker.js
  • server/server-entry.js
  • src/views/AreaView/components/ServiceFilterContainer/ServiceFilterContainer.js
  • package.json
  • src/layouts/EmbedLayout.js
  • src/components/ListItems/SimpleListItem/SimpleListItem.js
  • src/entry-client.jsx
  • src/redux/actions/services.js
  • scripts/update-runtime-env.js

📝 Walkthrough
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change as a Vite SSR migration.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/PL-240-vite-ssr

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.

@mikkojamG

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 8

Caution

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

⚠️ Outside diff range comments (1)
src/views/MapView/MapView.js (1)

9-10: ⚠️ Potential issue | 🟠 Major

React-Leaflet still runs in SSR; the current Leaflet-loading guard doesn’t prevent server failures.

src/views/MapView/MapView.js statically imports react-leaflet (import * as ReactLeaflet... and useMapEvents) on lines 9-10, and the map subtree also contains static react-leaflet imports (e.g. src/views/MapView/components/UserMarker/UserMarker.js). Deferring only Leaflet/plugin loading on lines 63-68 doesn’t stop server module evaluation; React-Leaflet/Leaflet touch window/DOM at load time and SSR must be disabled (wrap the whole map subtree in a client-only boundary or load it via dynamic import with ssr: false). (react-leaflet.js.org)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/views/MapView/MapView.js` around lines 9 - 10, MapView currently
statically imports react-leaflet (useMapEvents and the ReactLeaflet namespace)
and related components like UserMarker, causing SSR to evaluate Leaflet code and
crash; change this by removing those top-level imports and instead load the
entire map subtree on the client only — either dynamically import the MapView
map subtree (and components such as UserMarker) with SSR disabled (e.g.,
next/dynamic({ ssr: false })) or render the map subtree only after a client
mount check (useEffect/setState) so useMapEvents and ReactLeaflet are only
imported/executed in the browser; ensure all references to useMapEvents,
ReactLeaflet, and UserMarker are moved into the client-only module or the
dynamically imported component.
🧹 Nitpick comments (1)
package.json (1)

72-72: ⚡ Quick win

Align lint:fix extensions with lint.

lint handles .mjs, but lint:fix currently does not. Keep them consistent to avoid missed autofixes.

Suggested patch
-    "lint:fix": "eslint src/ server/ server.mjs --ext .js,.jsx --fix && prettier src/ server/ server.mjs --write",
+    "lint:fix": "eslint src/ server/ server.mjs --ext .js,.jsx,.mjs --fix && prettier src/ server/ server.mjs --write",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 72, The lint:fix npm script is missing the .mjs
extension so autofixes can miss files; update the "lint:fix" script in
package.json (the lint:fix entry) to include --ext .js,.jsx,.mjs for eslint and
ensure prettier invocation also targets .mjs (make the extensions consistent
with the "lint" script) so .mjs files are linted and auto-fixed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile`:
- Around line 6-11: The Dockerfile omits server.mjs from the build context so
the later production stage copy of /app/server.mjs into appbase/staticbuilder
fails; update the initial COPY steps to include server.mjs (the file named
server.mjs) into the image root (alongside the existing COPY of ./server and
./src) so that the downstream stage can successfully COPY /app/server.mjs —
ensure the same ownership flag (--chown=default:root) is used and that any
build-stage directory referenced by the production stage (appbase/staticbuilder)
will contain server.mjs.

In `@docs/ViteSSRMigrationPlan.md`:
- Around line 15-51: The fenced code blocks in docs/ViteSSRMigrationPlan.md are
missing language identifiers (e.g., the ASCII-art blocks and other snippets),
which triggers markdown lint MD040; update each triple-backtick fence used for
non-syntax blocks to include a language tag such as text (or bash where
appropriate). Locate the fenced blocks that contain the ASCII diagrams and
phase/service lists (the blocks shown in the diff) and change ``` to ```text (or
```bash for shell examples) so the renderer/linter recognizes them; ensure all
similar fenced blocks in the file (the ones showing diagrams and lists) are
updated consistently.

In `@scripts/update-runtime-env.js`:
- Line 23: The script always writes runtime overrides to the hardcoded outputDir
('public') so production (which serves dist/client) never gets the updated
env-config.js; change the outputDir assignment in scripts/update-runtime-env.js
(the outputDir constant) to resolve to 'dist/client' when running in production
(e.g. process.env.NODE_ENV === 'production') or, more robustly, detect and
prefer an existing 'dist/client' directory over 'public' before writing; ensure
the rest of the script still writes the env-config.js into the chosen outputDir.

In `@server.mjs`:
- Around line 203-210: The async route handlers that call await getEntry()
(e.g., the sitemap handler registered via app.use('/sitemap.xml', ...), the
robots handler app.get('/robots.txt', ...), and the other async app.get/app.use
handlers that also call getEntry()) must be wrapped so promise rejections are
forwarded to Express error middleware; wrap each async handler with the existing
asyncHandler helper (or implement a small wrapper that catches errors and calls
next(err)) so any rejection from getEntry() is passed to next rather than
causing an unhandled promise rejection.

In `@src/entry-client.jsx`:
- Around line 60-63: The current code replaces server-provided settings by
assigning preloadedState.settings = settings; instead of merging; change it so
localStorage settings overlay the server-preloaded settings by merging objects
(preserve existing preloadedState.settings keys and only overwrite with values
from SettingsUtility.getSettingsFromLocalStorage()), updating the assignment
that references SettingsUtility.getSettingsFromLocalStorage() and
preloadedState.settings accordingly.
- Around line 38-40: The tracePropagationTargets fallback produces [''] when
unset, causing Sentry to match all URLs; fix by normalizing and filtering the
value before passing it to Sentry: read config.sentryTracePropagationTargets,
split on ',', trim each entry, remove empty strings, and pass that filtered
array to the tracePropagationTargets option (the code around
tracePropagationTargets in src/entry-client.jsx). Ensure the transformation is
applied where tracePropagationTargets is constructed so untrimmed or empty
values are excluded.

In `@src/views/MapView/utils/mapActions.js`:
- Around line 9-11: panViewToBounds currently uses globalThis.L (and thus
depends on Leaflet being global) even though mapActions.js already loads Leaflet
into the module-scoped L; update panViewToBounds to use the module-scoped L
(e.g., replace globalThis.L.latLng / globalThis.L.latLngBounds uses with
L.latLng / L.latLngBounds) and add a guard to ensure L is non-null before
calling its methods so it works consistently without relying on globalThis.L or
side effects from MapView.js (which only sets globalThis.rL).

In `@vite.config.js`:
- Around line 9-42: The Vite config is using the wrong parameter name: change
the defineConfig callback parameter from ssrBuild to isSsrBuild and update the
conditional spread that currently uses !ssrBuild to use !isSsrBuild so
sentryVitePlugin is only added for non-SSR builds; update references inside the
defineConfig callback (e.g., the ternary that adds sentryVitePlugin) and ensure
the symbol names defineConfig and sentryVitePlugin remain unchanged.

---

Outside diff comments:
In `@src/views/MapView/MapView.js`:
- Around line 9-10: MapView currently statically imports react-leaflet
(useMapEvents and the ReactLeaflet namespace) and related components like
UserMarker, causing SSR to evaluate Leaflet code and crash; change this by
removing those top-level imports and instead load the entire map subtree on the
client only — either dynamically import the MapView map subtree (and components
such as UserMarker) with SSR disabled (e.g., next/dynamic({ ssr: false })) or
render the map subtree only after a client mount check (useEffect/setState) so
useMapEvents and ReactLeaflet are only imported/executed in the browser; ensure
all references to useMapEvents, ReactLeaflet, and UserMarker are moved into the
client-only module or the dynamically imported component.

---

Nitpick comments:
In `@package.json`:
- Line 72: The lint:fix npm script is missing the .mjs extension so autofixes
can miss files; update the "lint:fix" script in package.json (the lint:fix
entry) to include --ext .js,.jsx,.mjs for eslint and ensure prettier invocation
also targets .mjs (make the extensions consistent with the "lint" script) so
.mjs files are linted and auto-fixed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 459a2113-8509-4bad-96d5-c8bdc3c4977b

📥 Commits

Reviewing files that changed from the base of the PR and between 87acc39 and 90d70fd.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (45)
  • .babelrc
  • .eslintrc.json
  • Dockerfile
  • __mocks__/withStyles.js
  • client/client.js
  • compose.yml
  • config/index.js
  • config/package.json
  • docs/ViteSSRMigrationPlan.md
  • index.html
  • package.json
  • scripts/package.json
  • scripts/update-runtime-env.js
  • server.mjs
  • server/ieMiddleware.js
  • server/server-entry.js
  • server/server.js
  • server/sitemapMiddlewares.js
  • src/components/ListItems/SimpleListItem/SimpleListItem.js
  • src/components/ListItems/SuggestionItem/SuggestionItem.js
  • src/components/NewsInfo/components/NewsItem/NewsItem.js
  • src/components/SearchBar/SearchBarComponent.js
  • src/components/TabLists/TabLists.js
  • src/components/TopBar/DrawerMenu/DrawerMenu.js
  • src/createEmotionCache.js
  • src/entry-client.jsx
  • src/entry-server.jsx
  • src/layouts/EmbedLayout.js
  • src/store.js
  • src/views/AreaView/components/ServiceFilterContainer/ServiceFilterContainer.js
  • src/views/FeedbackView/FeedbackView.js
  • src/views/MapView/MapView.js
  • src/views/MapView/components/AddressMarker/AddressMarker.js
  • src/views/MapView/components/Districts/ParkingAreas.js
  • src/views/MapView/components/HideSidebarButton/HideSidebarButton.js
  • src/views/MapView/components/MarkerCluster/MarkerCluster.js
  • src/views/MapView/components/TransitStops/TransitStops.js
  • src/views/MapView/components/UserMarker/UserMarker.js
  • src/views/MapView/utils/createMap.js
  • src/views/MapView/utils/drawIcon.js
  • src/views/MapView/utils/mapActions.js
  • src/views/MapView/utils/swapCoordinates.js
  • src/views/MapView/utils/transitFetch.js
  • vite.config.js
  • vitest.config.js
💤 Files with no reviewable changes (5)
  • server/server.js
  • .babelrc
  • client/client.js
  • mocks/withStyles.js
  • server/ieMiddleware.js

Comment thread Dockerfile
Comment thread docs/ViteSSRMigrationPlan.md Outdated
Comment thread scripts/update-runtime-env.js Outdated
Comment thread server.mjs Outdated
Comment thread src/entry-client.jsx Outdated
Comment thread src/entry-client.jsx Outdated
Comment thread src/views/MapView/utils/mapActions.js
Comment thread vite.config.js Outdated
@mikkojamG mikkojamG force-pushed the feat/PL-240-vite-ssr branch 2 times, most recently from 607e81a to cf1dd83 Compare June 12, 2026 09:27
@azure-pipelines

Copy link
Copy Markdown

PALVELUKARTTA-UI branch is deployed to platta: https://palvelukartta-ui-pr1360.dev.hel.ninja 🚀🚀🚀

2 similar comments
@azure-pipelines

Copy link
Copy Markdown

PALVELUKARTTA-UI branch is deployed to platta: https://palvelukartta-ui-pr1360.dev.hel.ninja 🚀🚀🚀

@azure-pipelines

Copy link
Copy Markdown

PALVELUKARTTA-UI branch is deployed to platta: https://palvelukartta-ui-pr1360.dev.hel.ninja 🚀🚀🚀

@sonarqubecloud

Copy link
Copy Markdown

@mikkojamG mikkojamG force-pushed the feat/PL-240-vite-ssr branch from a1d8ebe to e4fd410 Compare July 2, 2026 10:38
@mikkojamG mikkojamG changed the title [WIP] PL-240 Vite SSR [WIP] PL-292 Vite SSR Jul 2, 2026
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

@azure-pipelines

Copy link
Copy Markdown

PALVELUKARTTA-UI branch is deployed to platta: https://palvelukartta-ui-pr1360.dev.hel.ninja 🚀🚀🚀

@mikkojamG mikkojamG changed the title [WIP] PL-292 Vite SSR PL-292 Vite SSR Jul 2, 2026
@mikkojamG mikkojamG marked this pull request as ready for review July 2, 2026 11:09
@mikkojamG mikkojamG requested a review from a team as a code owner July 2, 2026 11:09
@azure-pipelines

Copy link
Copy Markdown

PALVELUKARTTA-UI branch is deployed to platta: https://palvelukartta-ui-pr1360.dev.hel.ninja 🚀🚀🚀

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants