@scayle/storefront-nuxt2
TypeScript icon, indicating that this package has built-in type declarations

7.39.28 • Public • Published

Storefront Core Nuxt2

npm version npm downloads License Nuxt

This is the basis for setting up a new Storefront based on Nuxt 2. It provides a set of useful configurations and reusable composables, that can be used in the actual shop project.

Getting started

To start working with storefront-core, make sure to register it as a module in your nuxt.config.ts file:

import { NuxtConfig } from '@nuxt/types'

const config: NuxtConfig & { storefront: ModuleOptions } = {
  // ...

  modules: ['@scayle/storefront-nuxt2/dist/module/register'],

  storefront: {
    stores: [
      {
        domain: 'www.aboutyou.de',
        basePath: '/',
        bapi: {
          host: 'https://api-cloud.aboutyou.de/v1/',
          shopId: 139,
        },
        redis: {
          host: process.env.REDIS_HOST || 'localhost',
          port: process.env.REDIS_PORT
            ? parseInt(process.env.REDIS_PORT, 10)
            : 6379,
        },
      },
    ],
    imageBaseUrl: 'https://cdn.aboutstatic.com/',
  },
}

export default config

You can see all the possible configuration options here. Basically you are able to configure the BAPI backend, from which URL images should be served, caching providers and so forth.

Architecture

The storefront-core comprises a multitude of functionality. Generally speaking, the most important are:

  • API middleware + RPC methods
  • Composable functions like useWishlist
  • Helper functions for managing shop data like attributes
  • Adaption of nuxt/image with custom provider to load images from About You CDNs

How do the RPC methods work?

The RPC method calls use a hybrid approach. When you are on the server, they directly call the corresponding function. When you are calling the RPC method on the client, the call is serialized and sent to the API endpoint, which calls the function and then returns the return values.

Example:

import { rpcCall } from './rpcCall'
import { rpcMethods } from './index'

const returnedValue = await rpcCall(rpcMethods.getProductById, { id: 12 })

The benefit of this approach is, that we can use the same interface, regardless if the function is currently called in a SSR context on the server or client-side in the Vue application. If you are interested in how this is handled, take a look at the implementation

List of composables

Core: useRpc

One of the most used composable, although you will probably not use this composable directly. It provides a useful wrapper around the rpcCall method mentioned above, adding loading states that can easily be used from within your components.

Example:

import { useRpc } from '@scayle/storefront-nuxt2'
import { rpcMethods } from './index'

const { fetching, data, fetch } = useRpc(rpcMethods.getProductById)

await fetch({ id: 12 })

Methods for managing the wishlist - adding and removing items, etc.

import { useWishlist } from '@scayle/storefront-nuxt2'

const { addItem, removeItem, clearWishlist, isInWishlist, fetch } =
  useWishlist()

await fetch()

await addItem({ variantId: 123 })

Logging

How to set up

There are two places where we can set up a log.

  1. Injected into the nuxt context
  2. Provided in the req(request)-object for middleware support

IMPORTANT: You can very simply adapt the implementation from demo-shop. To do so, please have a look at the /modules/log folder.

Log for nuxt context

Create a new nuxt plugin and run Log.attachToNuxtContext.

declare module '@nuxt/types' {
  interface Context {
    $log: Log
  }
}

export default defineNuxtPlugin((context, inject) => {
  Log.attachToNuxtContext(inject, new Log({
      space: 'exampleProject',
      handler: (entry: LogEntry) => { ... },
  }))
}

IMPORTANT: Make sure this plugin is initialized on client-side AND on server-side.

Log for server middleware (no nuxt context)

Create a new server-middleware that runs before core.

export default (req: any, res: any, next: any) => {
  Log.attachToRequest(
    req,
    new Log({
      space: 'exampleProject',
      handler: (entry: LogEntry) => { ... },
    })
  )
  next()
}

How to make sure it works

If the setup did not work, your log messages will have the default space: "global". So your terminal will look like this:

DEBUG [global.checkout.logout] Destroy session and redirect user

If the setup was correct, you should see something like this:

DEBUG [exampleProject.checkout.logout] Destroy session and redirect user

Features

Spaces

To better determine, where the log messages originated from, you can pass a space to the log, as well as create sub-spaces very easily.

const log = new Log({ space: 'myApp' })
const subLog = log.space('login')

subLog.error('User not found.') // ERROR [myApp.login] User not found.

// or simply do:
log.space('login').error('User not found.') // ERROR [myApp.login] User not found.

Time (Track execution time)

You can track execution time by using the .time(...) method from Log. Here is an example how to use it:

return await log.time(`RPC [${methodName}]`, async () => {
  return (await axios.post(url, params, { headers })).data as TResult
})

Console output will look like this:

[demo.sfc.rpc] RPC [getFilters] took 6ms
[demo.sfc.rpc] RPC [getProductsByCategory] took 10ms

Caching

Storefront Core supports two modes of Caching. In general all cached entries are namespaced by shopId.

SSR Cache

The whole rendered HTML of the server is stored inside redis and on each request we check if we have a cache HIT. If an entry is found the HTML is returned and no page has to be rendered.

Wrapped Cache

Another possibility is to cache downstream API calls, for example for requesting product information. This is heavily used in all RPC methods. They way this works is that the expensive function is simply wrapped by the cached() method, like this:

return await cached(bapiClient.products.getById)(options.id, {
  with: options.with,
  campaignKey,
  pricePromotionKey: options.pricePromotionKey,
})

The wrapping method takes care of calculating a cache key (based on the name of the function wrapped and the passed arguments). If no cache is found the underlying expensive function gets called and the returned value will be cached. If an entry is found, this will be returned instead omitting the expensive function call.

Disable caching of specific pages

There are multiple ways to control the caching feature. First of all, the cache is only used when it is enabled via config:

// inside nuxt.config.ts
  storefront: {
    // [...]
    cache: {
      enabled: true,
      ttl: 60 * 60,
      paths: [],
      pathsDisabled: [],
    },
    // [...]

The properties paths and pathsDisabled allow you to select in a white- and blacklist matter which pages to allow or disallow. If no paths are specified, all SSR requests will be cached.

Warning: Make sure that no user-related data is rendered ON the server! Make sure that either a) this is initialized on the client (like the wishlist/basket/user information in the navigation) or b) these pages are forbidden to get cached (like the account area)! Otherwise you will leak user data to other people.

Tagging of Cache

The cache allows you to associate tags to rendered pages. This way it is very easy to invalidate all rendered pages that contain an updated product, for example.

The best place for adding tags to the cache is inside the onFetchAsync method. Here is an example:

const { data, fetch } = useProduct()
const { $cache } = useContext()

onFetchAsync(async () => {
  await fetchProduct(id)
  $cache.addTags([`product:${id}`])
})

Internally this works by creating a SET entry per tag, including all the cached keys that contain this tag. Example: Keys 'abc', 'def' and 'xyz' are stored but only 'abc' and 'def' are tagged with 'product:1001'. Executing SMEMBERS 'tag:product:1001' will produce ['abc', 'def'].

Invalidation of Cache

The cache module automatically registers a middleware (<host>/_cache/) with endpoints that you can trigger externally. This allows you to register webhooks or other apps to issue POST requests.

Purge entire cache

POST <host>/_cache/purge/all

Purge the entire cache, including all cached downstream API requests (BAPI, etc.)

Purge a specific tag

POST <host>/_cache/purge/tags Body: ["product:1001"]

Purge all cache entries that are tagged with product:1001.

Cache-Control headers

The cache module allows to set Cache-Control headers, so that CDN's like Cloudflare can cache the page on the edge. The only supported mode is with smax-age and stale-while-revalidate. If no maxAge and staleWhileRevalidate is configured, no cache-control header will be sent:

// nuxt.config.ts
storefront: {
  // [...]
  cache: {
    enabled: true,
    ttl: 60 * 60,
    sendCacheControlHeaders: true,
    maxAge: 60 * 60,
    staleWhileRevalidate: 60 * 60 * 24,
  }
  // [...]
}

Readme

Keywords

none

Package Sidebar

Install

npm i @scayle/storefront-nuxt2

Weekly Downloads

301

Version

7.39.28

License

MIT

Unpacked Size

404 kB

Total Files

321

Last publish

Collaborators

  • ay-henribeck
  • lukas-matejka-scayle
  • scayle-automation