> For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt.

# Tích hợp Vuex vào AEM

Đặc thù AEM: một trang có thể chứa nhiều AEM component, mỗi component mount một instance Vue riêng (nhiều mount point độc lập). Vấn đề: làm sao các instance này chia sẻ chung state. Vuex giải quyết bằng một store singleton dùng chung.

## Vấn đề nhiều mount point

```mermaid
graph TD
  Page[AEM Page] --> H["Header component (Vue instance 1)"]
  Page --> P["Product list (Vue instance 2)"]
  Page --> C["Mini cart (Vue instance 3)"]
  H -. cần đồng bộ cart .-> Store[(Vuex Store dùng chung)]
  P -. add to cart .-> Store
  C -. hiển thị cart .-> Store
```

Mỗi component là một `new Vue(...)` riêng. Nếu mỗi instance tự giữ state, mini cart không biết product list vừa thêm hàng. Truyền cùng một store vào mọi instance thì chúng chia sẻ chung dữ liệu.

## Store singleton dùng chung

Tạo store một lần, export ra để mọi bootstrapper import cùng instance đó.

```js
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'
import user from './modules/user'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: { cart, user }
})

export default store
```

## Bootstrapper inject store vào mọi instance

Mỗi mount point nhận cùng store qua option `store`.

```js
// main.js
import Vue from 'vue'
import store from './store'
import Header from './components/Header.vue'
import ProductList from './components/ProductList.vue'
import MiniCart from './components/MiniCart.vue'

const registry = {
  header: Header,
  'product-list': ProductList,
  'mini-cart': MiniCart
}

document.querySelectorAll('[data-vue-component]').forEach(el => {
  const Component = registry[el.getAttribute('data-vue-component')]
  if (!Component) return

  new Vue({
    store, // cùng một store cho mọi instance
    render: h => h(Component, {
      props: JSON.parse(el.dataset.props || '{}')
    })
  }).$mount(el)
})
```

Vì cùng một `store` object được truyền vào, mọi instance chia sẻ chung state. ProductList commit `cart/addItem`, MiniCart đọc `cart/itemCount` cập nhật ngay.

## Khởi tạo state từ Sling Model

Dữ liệu server-side (user đã đăng nhập, locale, context path, endpoint) nên đẩy vào store lúc khởi tạo thay vì gọi API thừa. HTL serialize JSON, clientlib parse và commit vào store.

```html
<!-- HTL: page component hoặc một sly riêng -->
<sly data-sly-use.model="com.nhansite.core.models.AppStateModel"/>
<script type="application/json" data-app-state>
    ${model.stateJson @ context='scriptString'}
</script>
```

```js
// đọc state ban đầu trước khi mount
const node = document.querySelector('[data-app-state]')
const initial = node ? JSON.parse(node.textContent) : {}

store.commit('user/setProfile', initial.user || null)
store.commit('app/setContextPath', initial.contextPath || '')
```

Lưu ý dùng `context='scriptString'` (hoặc thẻ script type application/json) để escape đúng khi nhúng JSON, tránh lỗi XSS và phá vỡ HTML.

## Endpoint và context path

Không hardcode URL API. AEM chạy dưới context path (vd `/content/site/vn`). Lấy base path và endpoint từ Sling Model, lưu vào store, action dùng khi gọi servlet.

```js
// module app
state: () => ({ contextPath: '', searchEndpoint: '' }),
mutations: {
  setContextPath(state, v) { state.contextPath = v },
  setSearchEndpoint(state, v) { state.searchEndpoint = v }
}
```

```js
// action trong module search
actions: {
  async run({ commit, rootState }, keyword) {
    const base = rootState.app.searchEndpoint
    const res = await fetch(`${base}?q=${encodeURIComponent(keyword)}`)
    commit('setResults', await res.json())
  }
}
```

## Author mode

Trong AEM Author, component có thể được thêm/xóa/refresh động qua editor. Lưu ý:

- Store singleton tồn tại theo vòng đời trang. Khi author chỉnh một component, AEM có thể chỉ refresh DOM của component đó, code mount lại cần idempotent (kiểm tra đã mount chưa, tránh tạo trùng instance).
- Tránh phụ thuộc thứ tự mount giữa các component. Khởi tạo state chung ở một nơi chạy sớm (page-level clientlib), không trong từng component.

```js
// guard tránh mount trùng khi author refresh
document.querySelectorAll('[data-vue-component]').forEach(el => {
  if (el.__vueMounted) return
  el.__vueMounted = true
  // ... mount
})
```

## Khi nào KHÔNG cần Vuex trong AEM

- Chỉ một AEM component Vue trên trang, state nội bộ: dùng props/events là đủ.
- Các component không chia sẻ state: không cần store chung.

Vuex thêm chi phí thiết lập và build. Chỉ dùng khi thật sự có state dùng chung xuyên nhiều mount point.

## Tóm tắt luồng

| Bước | Thực hiện                                                      |
| ---- | -------------------------------------------------------------- |
| 1    | Tạo store singleton, export từ một module                      |
| 2    | Bootstrapper import store, inject vào mọi `new Vue`            |
| 3    | Khởi tạo state ban đầu từ Sling Model JSON                     |
| 4    | Component đọc qua getter/mapState, ghi qua dispatch action     |
| 5    | Action gọi servlet bằng endpoint lấy từ store, commit mutation |
