Skip to content

02 — someli-platform stack

Two apps in one repo: a Nuxt 2 SPA and a React + Polotno sub-project. Plus a thick layer of CDN-loaded scripts.


The two apps

App Lives in Stack
Main app (the customer-facing SaaS UI) repo root Nuxt 2.18 / Vue 2.7 / SPA mode
Visual editor polotno-editor/ React 18 + Polotno + MobX-State-Tree (Parcel build)

The two are completely separate Node projects (separate package.json, separate node_modules, separate lockfiles). They share no source code. The editor is built into a single polotno-bundle.js (+ .css) at the repo root, and the Nuxt app imports it as an ES module:

import { createEditor } from '../../../../polotno-bundle'

Main app stack

Concern Choice Version
Framework Nuxt 2 2.18.1 (CLI), 2.15.8 (core)
View library Vue 2 2.7.16 (pinned)
Mode SPA — ssr: false in nuxt.config.js
State Vuex 3 (non-strict mode)
HTTP client @nuxtjs/axios ^5.13.6
Auth @nuxtjs/auth + local strategy ^4.9.1
Cookies cookie-universal-nuxt ^2.2.2
UI library Bootstrap-Vue 2 + Bootstrap 4 classes ^2.23.1
Build Nuxt 2's built-in webpack
Notifications vue-snotify
Forms filepond + plugins
Code style Prettier (.prettierrc)
Tests None. package.json test script just exit 1.

"SPA mode" means

ssr: false in nuxt.config.js makes Nuxt a build-time SPA generator, not a server. There is no Node-side rendering at request time. In production, yarn build produces a static SPA, and nginx serves it.

Junior gotcha: despite being a Nuxt app, asyncData and fetch lifecycle hooks run client-side only. Don't expect a SSR-style data-fetch behaviour.

Server middleware (small, easy to miss)

api/ contains tiny Nuxt serverMiddleware handlers (small Express routes injected via nuxt.config.js → serverMiddleware). The only currently-live endpoint is GET /api/youtube-videos in api/index.js. The rest is commented-out scaffolding.

Junior gotcha: api/ here is not the customer-facing API backend (that's someli-api). It is a Nuxt-internal serverMiddleware. Easy to confuse.


Polotno sub-project stack

Concern Choice
Framework React 18
Editor SDK Polotno (polotno for client, polotno-node lives in the BE)
State MobX-State-Tree
Build Parcel
Output polotno-bundle.js (+ .css) at the parent repo root

The Polotno editor is a separate React SPA that builds into a single JS+CSS bundle. The Nuxt app imports that bundle.

The customer Polotno editor and the designer Polotno editor have diverged. See ../../audit/CODE-OVERLAP-MATRIX.md § 6. Fixes do not auto-port.

For the full Polotno integration story: ../../audit/someli-platform/06-polotno-integration.md.


CDN-loaded scripts (head injection)

nuxt.config.js → head.script injects ~10 <script> tags loaded from external CDNs (rather than bundled):

  • jQuery — used by Bootstrap 4 components, slick-carousel, etc.
  • Bootstrap 4 bundle JS — Bootstrap-Vue brings the CSS; the JS comes from CDN
  • Stripe.js — for legacy Stripe billing
  • Paddle.js — for Paddle billing
  • Microsoft Clarity — user-session analytics
  • FontAwesome — icons
  • slick-carousel — carousel JS
  • Flagmeister — country flags
  • Snowfall — winter / seasonal effect

Junior gotcha:

  1. None of these are subject to SRI (no integrity attributes). A CDN compromise → script injection. Known platform-wide finding.
  2. CDN dependencies are not in package.json. Searching package.json for "jquery" finds nothing — but jQuery is loaded globally. Always look at nuxt.config.js for the full dependency picture.
  3. CDN scripts may load synchronously and block first paint. Performance budget is partly outside your bundle control.

Vuex store layout

store/ is the single most important state location.

Module What
store/index.js Root module (export const strict = false)
store/api.js Cache layer wrapping ~12 backend endpoints with a 5-minute TTL — fetchSubscription, fetchUserDetails, fetchSettings, fetchPosts(page), fetchFavPosts(page), …
store/post/ Post lifecycle state
store/dashboard/ Dashboard analytics
store/chat/ Chat / messaging
store/common/ Common shared state
store/plan/ Billing plans
store/reach/ Reach / impressions
store/onboarding/ Onboarding flow
store/leaderboard/ Leaderboard data
store/modules/sitemap/ Sitemap utilities

Components should prefer dispatching store/api/fetch* actions over calling $axios directly. The caching saves real API load and prevents UI flicker.

See ../../audit/someli-platform/05-state-management.md.


Identity, cookies, auth

Concern Where
Strategy @nuxtjs/auth v4, local strategy
Login endpoint webauthenticate (defined in nuxt.config.js → auth.strategies.local.endpoints.login)
User-fetch endpoint me
Logout Disabled at the strategy level (logout: false). The app calls app.$auth.logout() directly and clears cookies + localStorage itself.
Token storage Cookie (cookie-universal-nuxt)
Account ID Cookie (usedetail)

Identity lives in cookies, not in the Vuex store. The store sometimes mirrors cookie values, but cookies are the source of truth.

See ../../audit/someli-platform/03-auth-and-sessions.md.


Axios interceptor — adds auth headers

middleware/axios.js installs an axios request interceptor that:

  • Adds the token header (from cookie) — only for URLs starting with /auth/
  • Adds the accountId header (from usedetail cookie) — also only for /auth/
  • Adds a base64-encoded apptype header on every request

Consequence: if you call a backend endpoint not under /auth/, your request will be unauthenticated from the frontend's perspective. This is the opposite of what many backends assume — be careful which prefix your new endpoint sits under.


Router middleware chain

Declared in nuxt.config.js → router.middleware, runs on every navigation:

  1. middleware/redirect.js — decides where the user belongs (post-login routing)
  2. middleware/axios.js — installs the axios interceptor
  3. middleware/validUrl.js — records last-known-good path
  4. middleware/appendUTM.js — decorates outbound links with UTM parameters
  5. middleware/appendUrl.jsrewrites unprefixed URLs to /<accountId>/... for a hardcoded allow-list (/contentplanner, /dashboard/*, /billing, /userSetting, …)

Account-scoped routing: every meaningful URL is prefixed with accountId. /contentplanner is intentionally allowed as a bare URL; appendUrl will rewrite it to /<accountId>/contentplanner. The router middleware is doing real work — read it before "fixing" a redirect bug.

See ../../audit/someli-platform/04-routing.md.


Components

Auto-registered Yes (components: true in nuxt.config.js)
Folder components/, with subdirectories grouping related components
Notable subfolders Calender/ (sic), Charts/, Dashboard/, Buttons/, Cards/, Sidebars/

Auto-registration means you can use any component without an import statement — Nuxt picks it up by filename.


Layouts

Layout Used for
layouts/default.vue Main authenticated UI
layouts/error.vue 404 / error pages
layouts/onBoard.vue Onboarding flow
layouts/someliNetwork.vue "Someli Network" feature
layouts/specialOffer.vue Special-offer landing pages
layouts/fourOhOne.vue 401 redirect page

Pages opt in via the layout option:

<script>
export default { layout: 'onBoard' };
</script>

Plugins

plugins/ initialises client-only singletons. Most are declared ssr: false or mode: 'client' because the app doesn't SSR.

Examples: gtm.js (Google Tag Manager via @nuxtjs/gtm), hotjar.js, fullstory.js, clarity.js, salesforceiq.js, vue-snotify.js, Polotno-related client-only libs.


Build, lint, test

Build yarn build → static SPA in .nuxt/dist/
Lint Not configured
Format yarn format runs Prettier
Tests None
CI Jenkins only (no GitHub Actions in this repo)
Deploy Jenkins SSH-deploys to AWS Lightsail; production frontends mount node_modules from EFS at runtime (see start.sh)

Consequence for you: type and lint errors do not fail your PR. Manual verification in the browser is mandatory.


Next

03-architecture.md — the folder map and request lifecycle.