Skip to content

Routing & State Management

Status: Audit v0.1 (2026-05-09).

1. Routing

1.1 Library

The app uses Nuxt 2's file-based routing (no third-party router; vue-router is wrapped by Nuxt). Routes are derived from files under pages/.

Property Value Source
Library Nuxt 2's wrapper around vue-router 3 nuxt: 2.18.1 in package.json:67
File-based routing Yes pages/ directory
Route extension Possible via router.extendRoutes nuxt.config.js:228-240
Active-link CSS class node-active (non-default) nuxt.config.js:241
Layout transitions layout-leave/enter, mode out-in nuxt.config.js:257-260
Page transitions page-leave/enter, mode out-in nuxt.config.js:261-264

1.2 Route inventory

There are 84 page files (find pages -name '*.vue' -type f \| wc -l). Many of these are routes; some are sub-folders or _id-prefixed dynamic segments. The flat list of route paths (derived from find pages -name '*.vue' -type f \| sed 's\|^pages/\|\|; s\|/index\.vue$\|\|; s\|\.vue$\|\|' \| sort):

Public / unauthenticated routes (auth: false)

These pages opt out of @nuxtjs/auth. Confirmed via grep -rn "auth: false" pages \| wc -l = 14:

Route File Purpose
/ pages/index.vue Login / register landing page
/redirect pages/redirect.vue Post-login intermediate redirect skeleton
/callback pages/callback/index.vue OAuth callback handler
/socialAuth pages/socialAuth/index.vue Social-auth completion handler
/password_setup pages/password_setup/index.vue Password setup for invited users
/privacy pages/privacy/index.vue Privacy policy
/terms_and_conditions pages/terms_and_conditions.vue Terms
/plan pages/plan/index.vue Plan landing
/pay pages/pay.vue Legacy payment page
/fbDeletionStatus pages/fbDeletionStatus/index.vue Facebook compliance: data-deletion request status
/sample pages/sample/index.vue Sample/development page
/forgot_password pages/forgot_password/index.vue Password reset request
/reset pages/reset/index.vue Password reset confirmation
/heartbeat pages/heartbeat/index.vue Health-check ping page

Authenticated routes — account-scoped (pages/_accid/*)

These are the live, account-scoped routes. Cataloged via find pages/_accid -name 'index.vue' = 32 entries:

Route shape Purpose
/<accid>/contentplanner Content planner calendar
/<accid>/contentplanner/<id>/<id> Inline editor
/<accid>/dashboard/... Dashboards (overview, ROI, reach, leaderboard, hashtag-analytics, leads, new-connections, action-items, roadmap, website-traffic, google-reviews)
/<accid>/userSetting User settings (multi-tab)
/<accid>/userprofile User profile
/<accid>/Organisationprofile Organisation profile (onboarding step)
/<accid>/billing Billing
/<accid>/payment Payment / checkout
/<accid>/brandkit Brand kit
/<accid>/topics Topics
/<accid>/subjects_services Subjects & services (onboarding step)
/<accid>/goals_objectives Goals & objectives (onboarding step)
/<accid>/chaptername Chapter (BNI onboarding)
/<accid>/help_center Help center
/<accid>/my_library Image / content library
/<accid>/posteditor/<id> Post editor (Polotno)
/<accid>/cuseditor/<id> Custom-content editor (Polotno)
/<accid>/usereditor/<id> User-media editor (Polotno)
/<accid>/usermediaditor/<id> User-media editor variant (Polotno)
/<accid>/sharedposteditor/<id> Shared-post editor (Polotno)
/<accid>/templateeditor Template editor (Polotno)

Legacy / non-_accid authenticated routes

Several routes exist outside _accid and accept bare unprefixed URLs. The appendUrl middleware redirects bare hits to the _accid-prefixed version. Confirmed via middleware/appendUrl.js:6-36:

/contentplanner, /dashboard/*, /userSetting, /billing, /topics, /cuseditor, /usereditor, /posteditor, /subjects_services, /Organisationprofile, /brandkit, /payment, /chaptername, /goals_objectives, /usermediaditor, /sharedposteditor, /templateeditor, /my_library, /help_center.

These are legacy duplicates of the _accid-scoped routes. The middleware silently rewrites the URL, but the page files still exist and contain near-duplicate code (see architecture-overview.md § Finding WC-ARCH-2).

Other / unclassified

  • /designer — Polotno editor outside the _accid tree
  • /editor/<id> and /editor/<id>/<id> — generic editor variants
  • /users — possibly a dev/test page (pay.vue:18 references users.vue patterns)
  • /charts, /forms, /typography, /components, /components/tables, /components/notificationstemplate/sample pages from a Vue 2 admin-template starter, never deleted
  • /pages, /pages/my-profile — also template artifacts
  • /dayTimeSchedule, /roi, /social_accounts, /special_offer, /corporate_offer, /categories, /industry_news, /someli_network, /my_designs, /change_password, /carouseleditor*/ (3 variants), /carouselcpeditor — assorted feature surfaces

Finding [WC-RT-1] (Severity: Medium): The pages/ tree contains template/sample artifacts from the original admin-dashboard starter (/charts, /forms, /typography, /components, /components/tables, /components/notifications, /pages, /pages/my-profile, /users.vue, /typography.vue, /forms.vue). They appear to be live routes — verify whether they're reachable from the production app's navigation. [VERIFY-RT-1]: navigate the deployed app and check whether any of these are accessible. If not, they should be deleted (Phase 0).

Finding [WC-RT-2] (Severity: Medium): Three carousel-editor variants exist (carouseleditor, carouseleditor1, carouselcpeditor). [VERIFY-RT-2]: which is canonical? The 1 variant especially looks like a leftover.

1.3 The _accid dynamic segment

Most authenticated routes are nested under pages/_accid/. This is a Nuxt dynamic segment whose matched value is route.params.accid. URLs look like /<accountId>/contentplanner. After login, the redirect middleware constructs these paths from app.$auth.user.accountId.

Detail: see ../04-routing.md (existing project documentation).

1.4 Route auto-prefixing (middleware/appendUrl.js)

Every navigation runs through appendUrl middleware which checks localStorage.accountId and rewrites unprefixed URLs from the hardcoded list above into /<accountId>/.... Important caveats:

  1. Reads accountId from localStorage, not cookies. If localStorage.accountId is empty (e.g., direct URL hit without prior login flow), the middleware silently no-ops.
  2. The localStorage.accountId write is in middleware/head.js, which is not wired into the global middleware chain. So it's a real bug — see ../04-routing.md and ../03-auth-and-sessions.md.
  3. Editor routes get a positional shift/editor/<id> puts the accountId at index 1 instead of 0.

Finding [WC-RT-3] (Severity: High): The localStorage.accountId sync gap is a real, reproducible bug. Wiring head.js into the middleware chain or moving its localStorage.setItem('accountId', ...) line into redirect.js is the fix. Phase 0 effort.

1.5 Global router middleware

From nuxt.config.js:242:

router: {
  middleware: ['auth', 'axios', 'validUrl', 'appendUTM', 'appendUrl']
}
Slot File Effect Source
auth middleware/redirect.js Post-login decision tree (redirects to landing_url, payment, or /<accid>/contentplanner) nuxt.config.js:242, middleware/redirect.js
axios middleware/axios.js Installs an axios interceptor adding token/accountId/apptype headers; saves currentUrl cookie middleware/axios.js:1-17
validUrl middleware/validUrl.js Saves localStorage.fallback-path if the route resolves cleanly middleware/validUrl.js:1-7
appendUTM middleware/appendUTM.js Decorates <a> and <form> targets with UTM params on /topics middleware/appendUTM.js
appendUrl middleware/appendUrl.js Account-ID auto-prefix (see above) middleware/appendUrl.js

The auth slot does not point at the @nuxtjs/auth built-in middleware. It points at redirect.js. This is non-obvious and has surprised contributors.

There are also unwired middleware files: middleware/head.js, middleware/socialLogin.js. Per-page middleware: middleware/sociallink.js (used on 5 pages).

1.6 Route extensions

nuxt.config.js:228-240 adds two legacy redirects:

{ path: '/mission-control/overview', redirect: '/dashboard/overview' }
{ path: '/mission-control', redirect: '/dashboard' }

These accept old marketing-link URLs.

1.7 Code splitting

Nuxt 2 with webpack 4 automatically code-splits per page (each route gets its own JS chunk). The build produces 254 chunks (see architecture-overview.md §6 and performance.md).

There is no manual code splitting (import('./Heavy.vue') patterns) observed inside components. Lazy-loading inside a page is route-only.

The biggest single chunk is 4.99 MB raw / 1.37 MB gzipped (db8106d.js, contains react-dom + Polotno-related code). This is the chunk loaded on any editor page — it dominates the editor-route LCP.

1.8 Deep linking

Routes work for deep links because the SPA serves the same index.html for every URL (200.html in dist/, served by Nuxt at runtime). Specific cases:

  • Authenticated deep links survive a refresh because the redirect middleware re-runs on every navigation. Cookies are the source of truth (see ../03-auth-and-sessions.md).
  • State held only in component data is lost on refresh. No router-state-persistence library is in use.
  • Editor in-progress designs persist in localStorage keyed by pathname + hash — this is a Polotno feature, not the main app's router.

1.9 Error boundaries

Vue 2 has no <ErrorBoundary> component primitive. Vue 2.5+ supports errorCaptured lifecycle and Vue.config.errorHandler global hook. [VERIFY-RT-3]: search for errorCaptured and Vue.config.errorHandler in the codebase.

grep -rEn "errorCaptured|errorHandler" pages components plugins

(Run during Phase E verification.)

If neither is present, the app falls back to Nuxt's layouts/error.vue for top-level errors only — component-level errors will be silent or crash the page.

2. State management

2.1 Library

Property Value Source
Library Vuex 3 package.json:104
Strict mode Off store/index.js:1
Module structure Folder-per-module + single-file modules store/

2.2 Module inventory

store/
├── index.js                      Root module (non-strict)
├── api.js                        Central caching layer (this is the most important state file)
├── onboarding.js                 Onboarding flow state
├── chat/{actions,getters,mutations,state}.js
├── common/{actions,getters,mutations,state}.js
├── dashboard/{actions,getters,mutations,state}.js
├── leaderboard/{actions,getters,mutations,state}.js
├── plan/{actions,getters,mutations,state}.js
├── post/{actions,getters,mutations,state}.js
├── reach/{actions,getters,mutations,state}.js
└── modules/sitemap.js            Loaded specifically for sitemap generation

Existing project doc ../05-state-management.md goes deeper.

2.3 Global state shape (root)

From store/index.js:2-27:

user, socialdata, sociallinkdata, profileTab, companyLogo, userAccounts,
selectedAccount, userDb, pageTitle, pageDescription, sidebarVisible,
userBlockVisible, myPosts, myStatusPosts, plans, postDataSet, postArray,
isAccountSwitching, defaultCategoriess, userSetting, defaultAccount,
shouldSaveUserSettingModel, isSaveSettingLoaded, autodc

Notable observations:

  • user is rarely populated. The codebase reads identity from cookies (usedetail, accountId) and app.$auth.user, not from state.user. See ./authentication-client.md.
  • Spelling typo: defaultCategoriess (extra 's'). Confirms the field is referenced as-is across the codebase — renaming would break.
  • isAccountSwitching is a UX flag for AccountSwitcher.vue to suppress flicker.
  • autodc — meaning unclear. [VERIFY-RT-4]: what does autodc represent? SET_AUTODC mutation is in store/index.js:29-31.

2.4 The api module — central caching layer

store/api.js wraps ~12 backend endpoints in cache-with-TTL actions. Default TTL: 5 minutes (CACHE_DURATION = 5 * 60 * 1000). Caches:

  • Single-value: subscriptionCache, userDetailsCache, languagesCache, toneofVoiceCache, settingsCache, diyPostCache, allReelIdeasCache, accountDetailsCache, approvedDatesCache, libraryCache
  • Paginated (per-page): postsCache, favPostsCache, sharedImagesCache, imageLibraryCache

Each action returns cached data on error (silent failure). Force-refresh via { force: true }. Full invalidation via clearApiCache mutation.

fetchSubscription writes a subs cookie as a side effect — middleware/redirect.js reads that cookie. Bypassing the action by calling the endpoint directly skips the cookie write.

The onboarding module has its own cache with 3-minute TTL (ONBOARDING_DETAILS_CACHE_TTL), inconsistent with api.js's 5 minutes. See ../05-state-management.md.

2.5 Persistence

Vuex state itself is not persisted across reloads (no vuex-persistedstate or similar in deps). However:

  • Cookies persist identity (token, usedetail, accountId, ~20 others). See ./authentication-client.md.
  • localStorage persists accountId, loggedInUserId, profiletab, UTM params, fallback-path, and the Polotno editor's design JSON keyed by URL.

This split — Vuex transient + cookies/localStorage permanent — has multiple consequences. The most important: the user field in Vuex state is empty after refresh until something explicitly populates it. Code reading store.state.user will get null more often than not.

2.6 State management findings

Finding [WC-ST-1] (Severity: Medium): Vuex strict: false is set at the root (store/index.js:1). This silently allows direct state mutation from outside actions/mutations, defeating the main reason to use Vuex. Recommendation: flip to strict in dev mode (process.env.NODE_ENV !== 'production'); audit and fix violators.

Finding [WC-ST-2] (Severity: Medium): Caching TTL is inconsistent (api.js 5 min, onboarding.js 3 min). No central place defines TTLs. Recommendation: extract to a constant per resource type.

Finding [WC-ST-3] (Severity: Medium): The api.js cache silently returns stale data on error. Errors are not surfaced to the user or to error tracking (no Sentry — see ./observability.md). Network failures are invisible.

Finding [WC-ST-4] (Severity: High): There is no automatic cache invalidation after mutations. The pattern relies on the developer to dispatch a { force: true } re-fetch or clearApiCache. In a 154,779-line codebase, this is fragile — easy to miss in PRs. Existing project doc ../05-state-management.md calls this out as a maintenance burden.

3. Open questions

Tracked in ./verify-markers.md:

  • [VERIFY-RT-1] Are template/sample routes (/charts, /forms, /typography, etc.) reachable from the production app?
  • [VERIFY-RT-2] Which carousel-editor variant is canonical?
  • [VERIFY-RT-3] Are errorCaptured / Vue.config.errorHandler set anywhere?
  • [VERIFY-RT-4] What does autodc represent in root state?