Skip to content

03 Minimum Client SFC

In this section, we'll look at how to mount Vue App in client side. Full Code is available at 3-client-sfc

First, create a entry.client.ts

entry.client.ts

ts
import { createSSRApp } from 'vue'
import App from './App.vue'

const initApp = async () => {
  const app = createSSRApp(App)
  app.mount('#__nuxt')
}
initApp().catch(console.error)

Next, rewrite build function to build also entry.client.ts

We will use mergeConfig to merge defaultConfig and config specific to client and server as it is used in Nuxt.

vite.ts

ts
import { join } from 'node:path'
import { build as _build, mergeConfig, type InlineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export const build = async () => {
  try {
    const defaultConfig = {
      plugins: [vue()],
      build: {
        rollupOptions: {
          output: {
            format: 'esm',
            dir: join(import.meta.dirname, 'dist'),
          },
          preserveEntrySignatures: 'exports-only',
          treeshake: false,
        },
        emptyOutDir: false,
      },
    } satisfies InlineConfig

    const clientConfig = mergeConfig(defaultConfig, {
      build: {
        rollupOptions: {
          input: join(import.meta.dirname, 'entry.client.ts'),
          output: {
            entryFileNames: 'entry.client.js',
          },
        },
      },
    } satisfies InlineConfig)
    await _build(clientConfig)

    const severConfig = mergeConfig(defaultConfig, {
      build: {
        rollupOptions: {
          input: join(import.meta.dirname, 'entry.server.ts'),
          output: {
            entryFileNames: 'entry.server.js',
          },
        },
      },
    } satisfies InlineConfig)
    await _build(severConfig)

    console.log('Build completed successfully!')
  }
  catch (error) {
    console.error('Build failed:', error)
    process.exit(1)
  }
}

Add a id and script to html to mount the app in client side

render.ts

ts
function htmlTemplate({ HEAD, APP }: HtmlTemplateParams): string {
  return `
<!DOCTYPE html>
<html>
<head>
  ${HEAD}
</head>
<body>
  <div id="__nuxt">${APP}</div>
  <script type="module" src="/entry.client.js"></script>
</body>
</html>
  `
}

Serve client.entry.js script in specific route request

render.ts

ts
export const renderMiddleware = defineEventHandler(async (event) => {
  if (!renderer) await setupRenderer()

  const { req, res } = event.node
  if (req.url === '/entry.client.js') {
    const code = readFileSync(
      join(import.meta.dirname, 'dist/entry.client.js'),
      'utf-8',
    )
    res.setHeader('Content-Type', 'application/javascript')
    res.end(code)
  }

  const rendered = await renderer.renderToString({})
  const data = renderHTML(rendered)
  res.setHeader('Content-Type', 'text/html;charset=UTF-8')
  res.end(data, 'utf-8')
})

Run the server

If you run the server, you can see Count button works because we have mounted the app in client side.