You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/05/04 12:31:51 UTC

(grails-static-website) 09/10: Correct grails-vite-spa v8 CORS chapter + cross-reference grails-doc

This is an automated email from the ASF dual-hosted git repository.

jamesfredley pushed a commit to branch improve-v8-guides-pre-release
in repository https://gitbox.apache.org/repos/asf/grails-static-website.git

commit aa1526d3205d73185c5510fd2a542297d07642b2
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Mon May 4 08:30:54 2026 -0400

    Correct grails-vite-spa v8 CORS chapter + cross-reference grails-doc
    
    The cors.adoc chapter previously claimed there was nothing
    Grails-specific for CORS handling and pointed only at the Spring Boot
    CORS reference. That was wrong: grails-doc/src/en/guide/theWebLayer/cors.adoc
    documents Grails 8's first-class grails.cors.enabled configuration
    block, which produces a Spring CorsConfiguration mapped to /** by
    default and supports per-mapping overrides.
    
    The chapter now shows the canonical grails.cors block:
    
        grails:
            cors:
                enabled: true
                allowedOrigins:
                    - 'https://app.example.com'
                ...
    
    while keeping the original recommendation that the same-origin layout
    this guide builds removes the need for CORS entirely - the Grails
    config is only relevant if you split the SPA and the API across
    origins.
    
    Other chapter additions:
    * spaController.adoc cites the underlying source for the forward
      method (grails-controllers/src/main/groovy/grails/web/util/WebUtils.java)
      and the user-facing reference at
      grails-doc/src/en/guide/theWebLayer/controllers/forwarding.adoc.
    * urlMappings.adoc points at grails-doc/src/en/guide/theWebLayer/urlMappings.adoc
      and the restfulMappings sub-chapter.
    * devMode.adoc references grails-doc/src/en/guide/gettingStarted/developmentReloading.adoc
      for the Spring DevTools story.
    * whatYouWillBuild.adoc and backend.adoc add the matching
      grails-doc/src/en/guide/REST/ pointers.
    
    Every substantive chapter ends with a "Files touched" bullet list
    matching the established style.
    
    Assisted-by: claude-code:claude-4.6-opus
---
 guides/grails-vite-spa/v8/guide/backend.adoc       |  9 ++++++++-
 guides/grails-vite-spa/v8/guide/cors.adoc          | 22 +++++++++++++++++++++-
 guides/grails-vite-spa/v8/guide/devMode.adoc       |  6 +++++-
 guides/grails-vite-spa/v8/guide/frontend.adoc      |  7 +++++++
 guides/grails-vite-spa/v8/guide/layout.adoc        |  5 +++++
 guides/grails-vite-spa/v8/guide/prodBuild.adoc     |  4 ++++
 guides/grails-vite-spa/v8/guide/spaController.adoc |  6 ++++++
 guides/grails-vite-spa/v8/guide/urlMappings.adoc   |  6 ++++++
 guides/grails-vite-spa/v8/guide/viteConfig.adoc    |  4 ++++
 .../grails-vite-spa/v8/guide/whatYouWillBuild.adoc |  6 ++++++
 10 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/guides/grails-vite-spa/v8/guide/backend.adoc b/guides/grails-vite-spa/v8/guide/backend.adoc
index 4b1a39e15cf..4d272d4c81a 100644
--- a/guides/grails-vite-spa/v8/guide/backend.adoc
+++ b/guides/grails-vite-spa/v8/guide/backend.adoc
@@ -26,4 +26,11 @@ include::../snippets/backend/grails-app/views/book/_book.gson[]
 include::../snippets/backend/grails-app/views/book/index.gson[]
 ----
 
-This is exactly the pattern from the link:../grails-rest-library/8/guide/index.html[REST Library] guide, narrowed to a single domain so the Vite-and-React story stays in focus.
+This is exactly the pattern from the link:../grails-rest-library/8/guide/index.html[REST Library] guide, narrowed to a single domain so the Vite-and-React story stays in focus. The `RestfulController` and `.gson` view conventions are documented in `grails-doc/src/en/guide/REST/` (subchapters `domainResources.adoc`, `restRendering.adoc`).
+
+Files touched in this chapter:
+
+* `backend/grails-app/domain/example/Book.groovy`
+* `backend/grails-app/controllers/example/BookController.groovy`
+* `backend/grails-app/views/book/_book.gson`
+* `backend/grails-app/views/book/index.gson`
diff --git a/guides/grails-vite-spa/v8/guide/cors.adoc b/guides/grails-vite-spa/v8/guide/cors.adoc
index 527d11b423a..db43c5785af 100644
--- a/guides/grails-vite-spa/v8/guide/cors.adoc
+++ b/guides/grails-vite-spa/v8/guide/cors.adoc
@@ -3,9 +3,29 @@ CORS does not appear in either dev or prod for this layout - and that is the poi
 * *In dev*, the browser only ever talks to Vite on `:5173`. Vite forwards `/api/**` to Grails on `:8080` via its server-to-server proxy; the browser never sees a cross-origin request. No CORS preflight, no `Access-Control-Allow-Origin` headers.
 * *In prod*, the browser only ever talks to the bootJar on `:8080`. The SPA's static bundle is served from `/`; the API is served from `/api/**`. Both are the same origin. Same story: no CORS.
 
-For the case where the SPA and the API genuinely live on different origins (e.g. SPA on `app.example.com`, API on `api.example.com`), you would add a `CorsFilter` to the Grails side and configure allowed origins explicitly. The Spring Boot guide on link:https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web.servlet.spring-mvc.cors[CORS] is the canonical reference; nothing Grails-specific is required.
+For the case where the SPA and the API genuinely live on different origins (e.g. SPA on `app.example.com`, API on `api.example.com`), Grails ships a first-class CORS configuration block. From `grails-doc/src/en/guide/theWebLayer/cors.adoc` (the official 8.0.x reference):
+
+[source,yaml]
+.grails-app/conf/application.yml
+----
+grails:
+    cors:
+        enabled: true
+        allowedOrigins:
+            - 'https://app.example.com'
+        allowedMethods:
+            - GET
+            - POST
+        allowCredentials: true
+----
+
+The `grails.cors` block produces a Spring `CorsConfiguration` under the hood, mapped to `/**` by default. Per-mapping overrides are documented in the same `cors.adoc` chapter under the `mappings:` sub-key. The Spring Boot CORS documentation is the underlying reference for the option semantics (max-age, exposed headers, etc.).
 
 [NOTE]
 ====
 *Auth*: HTTP-only cookie sessions work transparently with this same-origin layout - the browser sends the session cookie on every request without any client-side wiring. JWT in `Authorization` headers is the alternative when the SPA is hosted independently of the API; it requires the SPA to attach the header explicitly on every fetch. The cookie-session path is recommended for browser-only SPAs unless you have a reason to pick JWT.
 ====
+
+Files touched in this chapter:
+
+* None (CORS only becomes relevant if you split the SPA and the API across origins)
diff --git a/guides/grails-vite-spa/v8/guide/devMode.adoc b/guides/grails-vite-spa/v8/guide/devMode.adoc
index 57036f56ef6..41e55ecf1fb 100644
--- a/guides/grails-vite-spa/v8/guide/devMode.adoc
+++ b/guides/grails-vite-spa/v8/guide/devMode.adoc
@@ -20,4 +20,8 @@ The browser opens `http://localhost:5173`. Every `fetch('/api/books')` call from
 Two things to keep an eye on:
 
 * *Same-origin in dev too.* Because the SPA always calls relative paths and Vite proxies them, the browser never sees a cross-origin request. CORS preflight requests do not happen even in dev.
-* *Spring DevTools for the backend, Vite HMR for the frontend.* Each side reloads independently; you can edit `App.jsx` without restarting Grails, and edit a Groovy controller without losing your SPA state.
+* *Spring DevTools for the backend, Vite HMR for the frontend.* Each side reloads independently; you can edit `App.jsx` without restarting Grails, and edit a Groovy controller without losing your SPA state. The Spring DevTools story for Grails 8 is covered in `grails-doc/src/en/guide/gettingStarted/developmentReloading.adoc`.
+
+Files touched in this chapter:
+
+* None directly (this chapter is a runbook)
diff --git a/guides/grails-vite-spa/v8/guide/frontend.adoc b/guides/grails-vite-spa/v8/guide/frontend.adoc
index 8534cfafd24..60d7b4e1b59 100644
--- a/guides/grails-vite-spa/v8/guide/frontend.adoc
+++ b/guides/grails-vite-spa/v8/guide/frontend.adoc
@@ -27,3 +27,10 @@ include::../snippets/frontend/src/App.jsx[]
 The single `fetch('/api/books')` call is the only contract between the two halves. The relative path means the same code runs in dev (where Vite proxies to `:8080`) and in prod (where the SPA is served from the bootJar at `:8080` already).
 
 To use Vue 3 instead of React, swap the dependency to `vue` + `@vitejs/plugin-vue` and replace `App.jsx` with an `App.vue` single-file component. The Gradle wiring, Vite proxy, and backend never change.
+
+Files touched in this chapter:
+
+* `frontend/package.json`
+* `frontend/index.html`
+* `frontend/src/main.jsx`
+* `frontend/src/App.jsx`
diff --git a/guides/grails-vite-spa/v8/guide/layout.adoc b/guides/grails-vite-spa/v8/guide/layout.adoc
index 2f08b7f7d11..99fbe3dacb3 100644
--- a/guides/grails-vite-spa/v8/guide/layout.adoc
+++ b/guides/grails-vite-spa/v8/guide/layout.adoc
@@ -21,3 +21,8 @@ Three patterns:
 * `npmInstall` -> `frontendBuild` -> `copyFrontendToBackend` is a chain. Each task declares its `inputs`/`outputs` so Gradle's up-to-date check skips the chain when nothing has changed.
 * `copyFrontendToBackend` writes into `backend/src/main/resources/public/`. Spring Boot serves anything under that directory at `/` automatically; no further wiring needed.
 * The `afterEvaluate { tasks.named('processResources').configure { dependsOn ... } }` line is what makes plain `./gradlew :backend:bootJar` (or `bootRun`) trigger the SPA build. Readers never type the frontend tasks directly.
+
+Files touched in this chapter:
+
+* `settings.gradle`
+* `build.gradle`
diff --git a/guides/grails-vite-spa/v8/guide/prodBuild.adoc b/guides/grails-vite-spa/v8/guide/prodBuild.adoc
index 7fcdcf5f31f..992c2636dc7 100644
--- a/guides/grails-vite-spa/v8/guide/prodBuild.adoc
+++ b/guides/grails-vite-spa/v8/guide/prodBuild.adoc
@@ -19,3 +19,7 @@ The resulting jar serves the SPA from `/` and the API from `/api/**`, both from
 ====
 The matching link:../grails-docker-bootbuildimage/8/guide/index.html[Docker bootBuildImage] guide turns this exact bootJar into an OCI image. Combined, the two guides give you `git push` -> tagged GHCR image -> single-container deploy.
 ====
+
+Files touched in this chapter:
+
+* None directly (this chapter is a runbook for the build chain wired up in `layout.adoc`)
diff --git a/guides/grails-vite-spa/v8/guide/spaController.adoc b/guides/grails-vite-spa/v8/guide/spaController.adoc
index 86b2bdfdde1..782a8147647 100644
--- a/guides/grails-vite-spa/v8/guide/spaController.adoc
+++ b/guides/grails-vite-spa/v8/guide/spaController.adoc
@@ -12,3 +12,9 @@ include::../snippets/backend/grails-app/controllers/example/SpaController.groovy
 ====
 *Why not a wildcard URL mapping like `'/$path**'(controller: 'spa', action: 'index')`?* Because URL mappings resolve before static resources. A wildcard mapping would intercept `/favicon.ico` and `/assets/main-abc123.js`, returning the SPA's `index.html` instead of the asset. Letting Spring Boot's static-resource handler claim everything except `/api/**` and `/` is the simplest correct shape.
 ====
+
+The `forward` method available on every Grails controller is the same one the framework uses internally for URL-mapping rewrites; see `grails-controllers/src/main/groovy/grails/web/util/WebUtils.java` for the underlying implementation and `grails-doc/src/en/guide/theWebLayer/controllers/forwarding.adoc` for the user-facing reference.
+
+Files touched in this chapter:
+
+* `backend/grails-app/controllers/example/SpaController.groovy`
diff --git a/guides/grails-vite-spa/v8/guide/urlMappings.adoc b/guides/grails-vite-spa/v8/guide/urlMappings.adoc
index 5960a728685..27a51491b33 100644
--- a/guides/grails-vite-spa/v8/guide/urlMappings.adoc
+++ b/guides/grails-vite-spa/v8/guide/urlMappings.adoc
@@ -10,3 +10,9 @@ include::../snippets/backend/grails-app/controllers/example/UrlMappings.groovy[]
 * Everything else - `/`, `/books/42`, `/about` - resolves to `SpaController.index`, which forwards to the bundled `index.html`. The SPA's client-side router takes over from there.
 
 The forward (rather than a redirect) is what makes deep links survive a browser refresh: a hard reload on `/books/42` returns the same `index.html` payload, the SPA boots, and React Router parses the URL.
+
+The URL-mapping DSL itself is covered in `grails-doc/src/en/guide/theWebLayer/urlMappings.adoc` and the per-method-routing story in `grails-doc/src/en/guide/theWebLayer/urlmappings/restfulMappings.adoc`.
+
+Files touched in this chapter:
+
+* `backend/grails-app/controllers/example/UrlMappings.groovy`
diff --git a/guides/grails-vite-spa/v8/guide/viteConfig.adoc b/guides/grails-vite-spa/v8/guide/viteConfig.adoc
index f695d3e0602..b05fc8cd5a2 100644
--- a/guides/grails-vite-spa/v8/guide/viteConfig.adoc
+++ b/guides/grails-vite-spa/v8/guide/viteConfig.adoc
@@ -12,3 +12,7 @@ Two pieces are doing the work:
 * `build.outDir = 'dist'` controls where `npm run build` writes the static bundle. Our root `build.gradle`'s `copyFrontendToBackend` task reads from this exact path.
 
 `emptyOutDir: true` makes a clean rebuild every time, so a renamed asset does not leave a stale predecessor in `dist/`.
+
+Files touched in this chapter:
+
+* `frontend/vite.config.js`
diff --git a/guides/grails-vite-spa/v8/guide/whatYouWillBuild.adoc b/guides/grails-vite-spa/v8/guide/whatYouWillBuild.adoc
index 8be6550058e..1769c4b5501 100644
--- a/guides/grails-vite-spa/v8/guide/whatYouWillBuild.adoc
+++ b/guides/grails-vite-spa/v8/guide/whatYouWillBuild.adoc
@@ -23,3 +23,9 @@ Two run-modes:
 
 * *Dev*: `./gradlew :backend:bootRun` in one shell, `cd frontend && npm run dev` in another. Vite serves the SPA on `:5173` with HMR; the proxy forwards `/api/**` to the Grails app on `:8080`.
 * *Prod*: `./gradlew :backend:bootJar`. Gradle runs `npm run build`, copies `frontend/dist/` into `backend/src/main/resources/public/`, and packages everything into one bootJar. The SPA serves from `/` and `/api/**` from the same origin - CORS irrelevant.
+
+The official Grails 8 reference manual (`grails-doc/src/en/guide/REST/`) covers the JSON-rendering side this guide consumes. The CORS chapter (`grails-doc/src/en/guide/theWebLayer/cors.adoc`) covers the `grails.cors.enabled` block this guide does NOT use - because the same-origin layout this guide builds removes the need for CORS entirely. Both chapters are worth knowing about.
+
+Files touched in this chapter:
+
+* None