ArkTS Server-Side Rendering (SSR) Practices
I. Introduction
In today's front-end development landscape, server-side rendering (SSR) is gaining traction for building high-performance web applications. ArkTS, with its strong expressiveness and rich features, enhances user experience when combined with SSR. This article explores ArkTS SSR practices, covering concepts, benefits, technical choices, development processes, code implementation, deployment, and performance optimization.
II. Concept and Benefits of SSR
2.1 Improved First-Screen Loading Speed
In traditional client-side rendering (CSR), users see a blank page until JavaScript loads and executes. With SSR, the server sends a complete HTML page to the browser, eliminating wait time for JavaScript and significantly reducing first-screen loading time.
2.2 Enhanced SEO
Search engine crawlers primarily parse static HTML content. In CSR, dynamic content generated by JavaScript may not be parsed correctly, affecting search rankings. SSR delivers complete HTML pages, making content easily crawlable and improving SEO.
III. Technical Choices for ArkTS SSR
3.1 Setting Up a Server Environment with Node.js
Node.js, based on Chrome's V8 engine, offers an efficient environment for server-side applications. To set up an ArkTS SSR project:
mkdir arktss-ssr-project
cd arktss-ssr-project
npm init -y
3.2 Framework Selection and Integration
Vue.js is a good choice for ArkTS SSR development. Install the required dependencies:
npm install vue @vue/server-renderer arkts-loader --save
Create an ArkTS component (HelloWorld.ets
):
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
return () => (
<h1>Hello, World!</h1>
);
},
});
IV. Development Process and Code Implementation
4.1 Server-Side Component Rendering
Use @vue/server-renderer
to render components on the server. Create a server.js
file:
const express = require('express');
const { createSSRApp } = require('vue');
const { renderToString } = require('@vue/server-renderer');
const HelloWorld = require('./HelloWorld.ets').default;
const app = express();
app.get('/', async (req, res) => {
const vueApp = createSSRApp(HelloWorld);
const html = await renderToString(vueApp);
const page =
;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ArkTS SSR Example</title>
</head>
<body>
${html}
<script src="/client.js"></script>
</body>
</html>
res.send(page);
});
const port = 3000;
app.listen(port, () => {
console.log(Server is running on port ${port}
);
});
4.2 Building an Isomorphic Rendering Pipeline
In production, sending only the initial HTML from the server is not enough; the browser must “take over” the page and make it interactive. This process is called hydration. To let Vue run both on the server and in the browser, you need to bundle the application twice: one bundle for renderToString
and one for the browser entry.
4.2.1 Browser-Side Entry client.js
import { createSSRApp } from 'vue';
import HelloWorld from './HelloWorld.ets';
// Create an app instance identical to the server one
const app = createSSRApp(HelloWorld);
// Mount to the DOM and trigger hydration
app.mount('#app');
The server-rendered HTML must contain a mount point
<div id="app">
; otherwise the browser cannot locate the root node and hydration will fail.
4.2.2 Build-Script Configuration
Use Vite or Webpack for dual-target bundling. Below is a minimal Vite 4 example:
vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import arkts from 'arkts-loader/vite';
export default defineConfig({
plugins: [vue(), arkts()],
build: {
rollupOptions: {
input: {
client: './src/client.ts',
server: './src/server.ts',
},
output: {
dir: 'dist',
format: 'cjs', // Server needs CommonJS
},
},
},
ssr: {
target: 'node',
},
});
After the build, dist/client.js
is referenced by a <script>
tag, while dist/server.js
serves as the Node entry.
4.3 Isomorphic Routing & State Management
4.3.1 Introducing Vue Router
Install the dependency:
npm i vue-router@4
Create router.ts
:
import { createMemoryHistory, createRouter, createWebHistory } from 'vue-router';
import Home from './pages/Home.ets';
import About from './pages/About.ets';
export default function createHistory(isServer = false) {
return isServer
? createMemoryHistory()
: createWebHistory();
}
export const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
];
export function createVueRouter(isServer = false) {
return createRouter({
history: createHistory(isServer),
routes,
});
}
4.3.2 Pre-Fetching Data on the Server
Using Pinia (or Vuex) as an example, fetch API data before rendering and serialize it into the HTML:
// server.ts snippet
import { renderToString } from '@vue/server-renderer';
import { createPinia } from 'pinia';
import { createSSRApp } from 'vue';
import createVueRouter from './router';
import { setActivePinia } from 'pinia';
import { useStore } from './stores/main';
export async function render(url: string) {
const app = createSSRApp(App);
const router = createVueRouter(true);
const pinia = createPinia();
app.use(router).use(pinia);
// Let router match the current URL first
await router.push(url);
await router.isReady();
// Collect asyncData hooks from matched components
const matched = router.currentRoute.value.matched;
const prefetchFns = matched.map(r => r.components?.default?.asyncData).filter(Boolean);
await Promise.all(prefetchFns.map(fn => fn({ store: useStore() })));
const html = await renderToString(app);
// Inject state into window.__INITIAL_STATE__
const state = `<script>window.__INITIAL_STATE__=${JSON.stringify(pinia.state.value)}</script>`;
return { html, state };
}
In client.ts
, deserialize __INITIAL_STATE__
to avoid duplicate requests.
4.3.3 Error Boundaries & Fallback Strategy
To prevent SSR failures from causing a blank white page, wrap the render call in a try-catch and fall back to CSR:
try {
const { html, state } = await render(req.url);
res.send(template.replace('<!--app-->', html).replace('<!--state-->', state));
} catch (e) {
console.error('SSR error:', e);
// Return a pure CSR page
res.send(template.replace('<!--app-->', '<div id="app"></div>').replace('<!--state-->', ''));
}
4.4 Performance Optimization Checklist
- Caching: Use an LRU cache for static-route results to avoid duplicate renders.
-
Streaming:
@vue/server-renderer
supportsrenderToNodeStream
, which improves TTFB by flushing HTML chunks early. -
Inlining: Inline critical CSS and first-screen data inside
<style>
and<script>
tags to reduce round-trips. -
CDN: Upload
dist/client
assets to a CDN and set long-termCache-Control
headers (e.g.,max-age=31536000, immutable
).
Top comments (0)