No More Posting New Topics!

If you have a question or an issue, please start a thread in our Github Discussions Forum.
This forum is closed for new threads/ topics.

Navigation

    Quasar Framework

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search

    SSR and Vuex meta data

    Help
    3
    19
    1050
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • PeterQF
      PeterQF last edited by

      Hi guys,
      I’m so frustrated, i can’t find any working solution that https://metatags.io/ accepts.

      I want to:

      1. load a post page
      2. get post data from API using slug in Vuex
      3. use the post title from Vuex in my page meta

      I have followed the documentation with the example “Some title” and this changes on page load to “Another title” but in https://metatags.io/ i get “Some title”.

      Please point me in the right direction, is there a complete working example anywere?

      Step 1 and 2 is working great but step 3 accessing the Vuex data to use in meta title won’t work. I just get undefined.

      Here are my codes:

      post.vue

      async preFetch ({ store, currentRoute, previousRoute, redirect, ssrContext }) {
          if (!ssrContext) {
            const payload = { post_slug: currentRoute.params.post }
            return await store.dispatch('posts/setPostObj', payload)
          }
        },
        computed: {
          ...mapState('posts', ['postObj', 'postMeta']),
          ...mapGetters('posts', ['getPostMeta']),
          ...mapGetters('settings', ['getLocale'])
        },
        mounted () {
          if (process.browser) {
            this.doSetPostObj()
          }
        },
        methods: {
          async doSetPostObj () {
            try {
              const payload = { post_slug: this.$route.params.post }
              return await this.$store.dispatch('posts/setPostObj', payload).then(() => {
                document.title = `${this.postMeta.title} - ${this.$t('general.appName')}`
              })
            } catch (error) {
              this.$router.replace('/error')
            }
          }
        },
        meta () {
          return {
            title: this.getPostMeta.title,
            meta: {
              title: {
                name: 'title',
                content: this.getPostMeta.title
              },
              description: {
                name: 'description',
                content: this.getPostMeta.description
              },
              ogType: {
                property: 'og:type',
                content: 'website'
              },
              ogUrl: {
                property: 'og:url',
                content: this.getPostMeta.ogUrl
              },
              ogTitle: {
                property: 'og:title',
                content: this.getPostMeta.title
              },
              ogDesc: {
                property: 'og:description',
                content: this.getPostMeta.description
              },
              ogImage: {
                property: 'og:image',
                content: this.getPostMeta.ogImage
              },
              twitterTitle: {
                name: 'twitter:title',
                content: this.getPostMeta.title
              },
              twitterDesc: {
                name: 'twitter:description',
                content: this.getPostMeta.description
              }
            }
          }
      

      Vuex action

      export const setPostObj = async function (context, payload) {
        return axiosInstance.get('api/site/getPostObj', {
          params: { post_slug: payload.post_slug }
        }).then(({ data }) => {
          context.commit('setPostObj', data)
          context.commit('setPostMeta', data)
        })
      }
      
      beets 1 Reply Last reply Reply Quote 0
      • beets
        beets @PeterQF last edited by beets

        @PeterQF Try changing your component code to this:

          async preFetch ({ store, currentRoute, previousRoute, redirect, ssrContext }) {
            try {
              const payload = { post_slug: currentRoute.params.post }
              await store.dispatch('posts/setPostObj', payload)
            } catch(error) {
              redirect('/error')
            }
          },
          computed: {
            ...mapState('posts', ['postObj', 'postMeta']),
            ...mapGetters('posts', ['getPostMeta']),
            ...mapGetters('settings', ['getLocale'])
          },
          meta () {
            return {
              title: this.getPostMeta.title,
              meta: {
                title: {
                  name: 'title',
                  content: this.getPostMeta.title
                },
                description: {
                  name: 'description',
                  content: this.getPostMeta.description
                },
                ogType: {
                  property: 'og:type',
                  content: 'website'
                },
                ogUrl: {
                  property: 'og:url',
                  content: this.getPostMeta.ogUrl
                },
                ogTitle: {
                  property: 'og:title',
                  content: this.getPostMeta.title
                },
                ogDesc: {
                  property: 'og:description',
                  content: this.getPostMeta.description
                },
                ogImage: {
                  property: 'og:image',
                  content: this.getPostMeta.ogImage
                },
                twitterTitle: {
                  name: 'twitter:title',
                  content: this.getPostMeta.title
                },
                twitterDesc: {
                  name: 'twitter:description',
                  content: this.getPostMeta.description
                }
              }
            }
        
        

        Edit: From looking at the code, your component actually never called the posts/setPostObj action on the server, because in preFetch you have if(!ssrContext) and the mounted hook doesn’t run on server side with SSR (only beforeCreate and created.) The server must have the post object to set the meta tags, else it won’t be picked up by metatags.io

        1 Reply Last reply Reply Quote 0
        • PeterQF
          PeterQF last edited by

          @beets thank you for answering. Any idea how i can solve this problem with the server issue?

          beets 1 Reply Last reply Reply Quote 0
          • beets
            beets @PeterQF last edited by

            @PeterQF Did you try the code I posted? It should work to fix the meta tag issue.

            1 Reply Last reply Reply Quote 0
            • PeterQF
              PeterQF last edited by

              @beets , yes i have and i get:

              response: undefined,
              isAxiosError: true,

              when i skip:

              if (!ssrContext) {…

              beets 1 Reply Last reply Reply Quote 0
              • beets
                beets @PeterQF last edited by

                @PeterQF What if you try to use the full URL here:

                  return axiosInstance.get('api/site/getPostObj', {
                

                i.e. https://example.com/api/site/getPostObj

                1 Reply Last reply Reply Quote 0
                • PeterQF
                  PeterQF last edited by

                  @beets i need to include API key in header so this is my code in boot/axios.js:
                  ´´´
                  const axiosInstance = axios.create({
                  baseURL: process.env.PROD
                  ? ‘https://api.lsvab.se’
                  : ‘http://api-lsvab.lndo.site’,
                  timeout: 180000,
                  headers: {
                  ‘X-Client-Api-Key’: ‘XXXXXXXXXXXXXXXXXXXXXXXXXX’,
                  ‘Content-Type’: ‘application/json’
                  }
                  })
                  ´´´

                  beets 2 Replies Last reply Reply Quote 0
                  • beets
                    beets @PeterQF last edited by

                    @PeterQF I see. What I can say for sure is that you don’t want if (!ssrContext) { in your prefetch, or the meta tags won’t work. We need to figure out why axios gives an error, can you provide the full output (from the terminal window running quasar dev) if you put a console.log here:

                      async preFetch ({ store, currentRoute, previousRoute, redirect, ssrContext }) {
                        try {
                          const payload = { post_slug: currentRoute.params.post }
                          await store.dispatch('posts/setPostObj', payload)
                        } catch(error) {
                          console.log(error)
                          redirect('/error')
                        }
                      },
                    
                    1 Reply Last reply Reply Quote 1
                    • beets
                      beets @PeterQF last edited by

                      @PeterQF Also, do you control that API endpoint? Is it possibly checking for a hostname to equal some value? If so, you may need to set the host header:

                      const axiosInstance = axios.create({
                      baseURL: process.env.PROD
                      ? 'https://api.lsvab.se’'
                      : 'http://api-lsvab.lndo.site',
                      timeout: 180000,
                      headers: {
                      'X-Client-Api-Key': 'XXXXXXXXXXXXXXXXXXXXXXXXXX',
                      'Content-Type': 'application/json',
                      'Host': 'frontendurl.com'
                      }
                      })
                      

                      Of course, setting Host only can be done in the server process. I don’t think it will affect anything being there on the client, but if so, you’d just have to check which environment you’re in and conditionally add it. I.e if(process.env.SERVER)

                      1 Reply Last reply Reply Quote 0
                      • PeterQF
                        PeterQF last edited by

                        @beets ok so if i understand you correct preFetch is to be used on both client and server page load?

                        Adding Host: ‘api-lsvab.lndo.site’

                        gives me:

                        Refused to set unsafe header “Host”

                        beets 1 Reply Last reply Reply Quote 0
                        • PeterQF
                          PeterQF last edited by

                          @beets WOW, major breakthrough.

                          Turned out that my local env had a problem with Axios calls in preFetch due to Lando/Docker.

                          Removing

                          if (!ssrContext) {

                          works great on production server and https://metatags.io/ shows all meta correct.

                          Thank you so much for making me focus on preFetch.

                          I will just figure out how to “allow” Axios calls to local Lando/Docker API or maybe just disable preFetch locally since the meta isn´t that important under development.

                          1 Reply Last reply Reply Quote 1
                          • PeterQF
                            PeterQF last edited by PeterQF

                            By the way, console.log(error) when running preFetch in my dev env gives me this error:

                            Error: connect ECONNREFUSED 172.20.0.4:80
                                at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1054:14) {
                              errno: 'ECONNREFUSED',
                              code: 'ECONNREFUSED',
                              syscall: 'connect',
                              address: '172.20.0.4',
                              port: 80,
                              config: {
                                url: 'api/site/getPostObj',
                                method: 'get',
                                headers: {
                                  Accept: 'application/json, text/plain, */*',
                                  'X-Client-Api-Key': 'XXXXXXXXXXXXXXXXXX,
                                  'Content-Type': 'application/json',
                                  'User-Agent': 'axios/0.21.0'
                                },
                                params: { post_slug: 'produktunderkategori-nyhet' },
                                baseURL: 'http://api-lsvab.lndo.site',
                                transformRequest: [ [Function: transformRequest] ],
                                transformResponse: [ [Function: transformResponse] ],
                                timeout: 180000,
                                adapter: [Function: httpAdapter],
                                xsrfCookieName: 'XSRF-TOKEN',
                                xsrfHeaderName: 'X-XSRF-TOKEN',
                                maxContentLength: -1,
                                maxBodyLength: -1,
                                validateStatus: [Function: validateStatus],
                                data: undefined
                              },
                              request: Writable {
                                _writableState: WritableState {
                                  objectMode: false,
                                  highWaterMark: 16384,
                                  finalCalled: false,
                                  needDrain: false,
                                  ending: false,
                                  ended: false,
                                  finished: false,
                                  destroyed: false,
                                  decodeStrings: true,
                                  defaultEncoding: 'utf8',
                                  length: 0,
                                  writing: false,
                                  corked: 0,
                                  sync: true,
                                  bufferProcessing: false,
                                  onwrite: [Function: bound onwrite],
                                  writecb: null,
                                  writelen: 0,
                                  bufferedRequest: null,
                                  lastBufferedRequest: null,
                                  pendingcb: 0,
                                  prefinished: false,
                                  errorEmitted: false,
                                  emitClose: true,
                                  autoDestroy: false,
                                  bufferedRequestCount: 0,
                                  corkedRequestsFree: [Object]
                                },
                                writable: true,
                                _events: [Object: null prototype] {
                                  response: [Array],
                                  error: [Function: handleRequestError],
                                  timeout: [Function]
                                },
                                _eventsCount: 3,
                                _maxListeners: undefined,
                                _options: {
                                  maxRedirects: 21,
                                  maxBodyLength: 10485760,
                                  protocol: 'http:',
                                  path: '/api/site/getPostObj?post_slug=produktunderkategori-nyhet',
                                  method: 'GET',
                                  headers: [Object],
                                  agent: undefined,
                                  agents: [Object],
                                  auth: undefined,
                                  hostname: 'api-lsvab.lndo.site',
                                  port: null,
                                  nativeProtocols: [Object],
                                  pathname: '/api/site/getPostObj',
                                  search: '?post_slug=produktunderkategori-nyhet'
                                },
                                _ended: true,
                                _ending: true,
                                _redirectCount: 0,
                                _redirects: [],
                                _requestBodyLength: 0,
                                _requestBodyBuffers: [],
                                _onNativeResponse: [Function],
                                _currentRequest: ClientRequest {
                                  _events: [Object: null prototype],
                                  _eventsCount: 7,
                                  _maxListeners: undefined,
                                  outputData: [],
                                  outputSize: 0,
                                  writable: true,
                                  _last: true,
                                  chunkedEncoding: false,
                                  shouldKeepAlive: false,
                                  useChunkedEncodingByDefault: false,
                                  sendDate: false,
                                  _removedConnection: false,
                                  _removedContLen: false,
                                  _removedTE: false,
                                  _contentLength: 0,
                                  _hasBody: true,
                                  _trailer: '',
                                  finished: true,
                                  _headerSent: true,
                                  socket: [Socket],
                                  connection: [Socket],
                                  _header: 'GET /api/site/getPostObj?post_slug=produktunderkategori-nyhet HTTP/1.1\r' +
                                    '\nAccept: application/json, text/plain, */*\r' +
                                    '\nX-Client-Api-Key: XXXXXXXXXXXXXXXXX\r' +
                                    '\nContent-Type: application/json\r' +
                                    '\nUser-Agent: axios/0.21.0\r' +
                                    '\nHost: api-lsvab.lndo.site\r' +
                                    '\nConnection: close\r' +
                                    '\n\r' +
                                    '\n',
                                  _onPendingData: [Function: noopPendingOutput],
                                  agent: [Agent],
                                  socketPath: undefined,
                                  method: 'GET',
                                  path: '/api/site/getPostObj?post_slug=produktunderkategori-nyhet',
                                  _ended: false,
                                  res: null,
                                  aborted: false,
                                  timeoutCb: null,
                                  upgradeOrConnect: false,
                                  parser: null,
                                  maxHeadersCount: null,
                                  _redirectable: [Circular],
                                  [Symbol(isCorked)]: false,
                                  [Symbol(outHeadersKey)]: [Object: null prototype]
                                },
                                _currentUrl: 'http://api-lsvab.lndo.site/api/site/getPostObj?post_slug=produktunderkategori-nyhet',
                                _timeout: Timeout {
                                  _idleTimeout: -1,
                                  _idlePrev: null,
                                  _idleNext: null,
                                  _idleStart: 29830654,
                                  _onTimeout: null,
                                  _timerArgs: undefined,
                                  _repeat: null,
                                  _destroyed: false,
                                  [Symbol(refed)]: null,
                                  [Symbol(asyncId)]: 796876,
                                  [Symbol(triggerId)]: 796873
                                }
                              },
                              response: undefined,
                              isAxiosError: true,
                              toJSON: [Function: toJSON]
                            }
                            

                            any idea how to solve this?

                            beets 1 Reply Last reply Reply Quote 0
                            • beets
                              beets @PeterQF last edited by

                              @PeterQF I’m not really well versed in docker. Is your SSR server and API server on different containers I suppose? You may need to set something with hosts to allow the SSR process to reach the API.

                              For me, without docker, I set baseURL to 127.0.0.1:8000 on the server, and https://api.example.com client. So if you can access the api with an internal network IP that could work, as long as docker is set up right. In this case you would have four checks:

                              If production and is server
                              If production and is client
                              If staging and is server
                              If staging and is client

                              And choose a host name / ip that can be reached on each of those cases.

                              As for the Host header, if you weren’t restricting api calls by that header, you don’t need it. But if you are, then just don’t set that on client. What I would do is some like:

                              const axiosConfig = {
                              headers:{
                              // your api key, and common stuff
                              }
                              }

                              if(process.env.SERVER) {
                              axiosConfig.headers.Host = “frontendurl.com”
                              if(process.env.NODE_ENV === “development”) {
                              axiosConfig.baseURL = “something”
                              } else {
                              axiosConfig.baseURL = “something else”
                              }
                              } else {
                              // repeat dev or prod check from above
                              }

                              On mobile, excuse any typos

                              1 Reply Last reply Reply Quote 0
                              • beets
                                beets @PeterQF last edited by

                                @PeterQF also, yes, prefetch runs on either client or server. Initial page load causes prefetch to happen on server, while navigating to that page after the initial load causes it to happen on the client

                                1 Reply Last reply Reply Quote 0
                                • PeterQF
                                  PeterQF last edited by PeterQF

                                  So guys,
                                  This is how i solved this.

                                  Turned out that Lando/Docker needed another base url when connecting to server with preFetch so the simple solution was to use this code for baseURL:

                                  baseURL: process.env.PROD
                                      ? 'https://api.lsvab.se'
                                      : process.env.SERVER && process.env.DEV ? 'http://api-server.apilsvab.internal:8000' : 'http://api-lsvab.lndo.site',
                                  

                                  Hope it can help anyone using Lando/Docker.

                                  1 Reply Last reply Reply Quote 0
                                  • S
                                    Sfinx last edited by

                                    The problem with docker is that binding to the 127.0.0.1:xxx will not work - you must bind to 0.0.0.0:xxx. It is well known trap

                                    1 Reply Last reply Reply Quote 0
                                    • PeterQF
                                      PeterQF last edited by PeterQF

                                      Well,
                                      Pure Docker might accept 0.0.0.0:xxx but not using Lando.
                                      With Lando one has to connect to xxx.internal:xxxx

                                      Just like with DB connection: DB_HOST=default.postgres96.internal

                                      1 Reply Last reply Reply Quote 0
                                      • S
                                        Sfinx last edited by

                                        Connection target and binding one are different things. At docker you must bind server to 0.0.0.0 and connect client using node host address which is usually internal to docker network

                                        1 Reply Last reply Reply Quote 1
                                        • PeterQF
                                          PeterQF last edited by

                                          @Sfinx i’m sorry if i mix things up.
                                          I’m really not that skilled with Docker, that is why i use Lando as it takes care of all this.
                                          Just had to figure out how Lando wants me to do Axios calls in different situations.

                                          1 Reply Last reply Reply Quote 0
                                          • First post
                                            Last post