Improving Nuxt SSR Stability for iQIYI Frontend: Performance, Caching, Rate Limiting, Disaster Recovery, and Logging
To boost iQIYI’s front‑end reliability, the team replaced a Velocity‑based SSR with Nuxt, introduced a centralized page‑config plugin, streamlined legacy‑browser handling, built a visual data‑filtering API, implemented Nginx and component caching, purge endpoints, multi‑layer rate limiting, disaster‑recovery fallback, and comprehensive logging, achieving ~0.5 s first‑screen loads, 0.2 % error rate and near‑100 % availability.
Background
Providing a stable and timely page experience is a core goal for front‑end engineers. iQIYI’s main site frequently adds or removes programs and configures activities, which puts high demands on the availability and stability of its SSR service.
Before 2019 the SSR was implemented with Velocity templates inside a CMS platform. While rendering was fast, the approach suffered from several serious drawbacks:
(1) Poor development experience in the CMS (no IDE, no shortcuts, no plugins). (2) Mismatched front‑end and back‑end code structures (Velocity vs. Vue). (3) Vue component encapsulation was broken because Vue components had to be written as Slots in the CMS for SEO/SSR.
To address these issues the team switched to a Node‑based SSR using Nuxt, which aligns with the Vue front‑end framework.
Nuxt Stability Improvements
2.1 Page Configuration
A central configuration file is placed at the project root. It exports an object whose keys are router names and whose values contain page‑specific settings such as cache policy, purge URLs, theme, and ad information.
export default {
'dianshiju-id': {...},
'zongyi': {
theme: 'dark', // page theme configuration
// ... other settings
},
'home2020': {...},
'rank-hot': {...}
}In a Nuxt plugin the configuration is injected into every component instance based on the current route, allowing any component to access the page settings without prop drilling.
import config from 'configs/pageinfo.js'
export default ({ route }, inject) => {
inject('pageInfo', config[route.name]) // inject page configuration
}Components can then use the injected data directly, e.g.:
<div :class="$pageInfo.theme">我是综艺页面</div>2.2 Browser Compatibility
Although Nuxt can theoretically support IE9, the team dropped IE9‑ support to keep the codebase clean. A custom render:route hook removes high‑version scripts and injects jQuery and a low‑version entry script for legacy browsers.
// nuxt.config.js
'render:route': (url, result, { req }) => {
if (isLowBrowser(req)) {
const $ = cheerio.load(result.html)
$('body script[src*=\'pcw/ssr\']').remove() // remove high‑version js
$('body').append('
') // add jquery
$('body').append('
') // add low‑version entry js
result.html = $.html()
}
}2.3 Performance Optimization
Nuxt attaches the result of every asyncData call to window.__NUXT__ . Because the size of this payload directly impacts transfer time and HTML size, three approaches were evaluated:
Filtering data inside asyncData (reduces HTML size but not network payload).
Using GraphQL (eliminates redundant data but introduces heavy query strings and maintenance overhead).
Building a visual data‑filtering platform that maps source fields to the required output, generating a lightweight API without manual GraphQL strings.
The data‑filtering platform proved the most maintainable and efficient.
2.4 Caching
Page‑level caching is handled by Nginx reverse proxy (default 5‑minute TTL, fallback to stale cache on non‑200 responses). Component‑level caching uses the official @nuxtjs/component-cache module with a custom serverCacheKey derived from a cacheKey prop.
2.5 Purge
A purge API is exposed via Koa router to clear both Nginx and CDN caches for a given page name. The page configuration file links page names to the URLs that must be purged.
// server/index.js
const app = new Koa()
const router = new Router()
router.get('/api/purge/page/:pageName', async (ctx) => {
ctx.body = await purgePage(ctx) // purge nginx and CDN caches
})
app.use(router.routes())
app.use(ctx => { nuxt.render(ctx.req, ctx.res) }) // configs/pageinfo.js
zongyi: {
purge: {
purgeUrl: [
'https://zongyi.iqiyi.com/',
'https://www.iqiyi.com/zongyi'
]
}
}NGINX is configured to allow purge requests:
location / {
proxy_cache_purge PURGE from all;
}2.6 Rate Limiting
Three layers protect against abusive traffic:
WAF (company firewall) filters malicious requests.
Single‑IP rate limiting via Nginx limit_req (different limits for normal users and crawlers, excess requests receive 503).
IP blacklist based on log analysis. Real client IP is obtained from X‑Forwarded‑For and trusted gateway IP ranges are added via real_ip_header and set_real_ip_from .
# nginx.conf
server {
real_ip_header X-Forwarded-For; # client IP stored in X-Forwarded-For
real_ip_recursive on;
set_real_ip_from 10.0.0.0/8; # trust internal network
include ip-blacklist.conf;
}
# ip-blacklist.conf
deny 203.0.113.45;2.7 Disaster Recovery
An independent disaster‑recovery service periodically fetches all important pages. If the primary service returns non‑200, Nginx proxies the request to the cached HTML from the DR service, ensuring near‑100% availability.
2.8 Server Logging
Two log streams are recorded:
Page rendering logs (info and error) with URL, referrer, cookies, IP, and a generated requestId .
API request logs (info and error) that include the originating page URL, API endpoint, parameters, and the same requestId .
// nuxt.config.js
hooks: {
'render:setupMiddleware': app => {
app.use(async (req, res, next) => {
req.logParams = { requestId: generateRandomString(), pageUrl: req.url }
next()
})
},
'render:routeDone': (url, result, { req }) => {
logger.page.info({ type: 'render', ...req.logParams }, req)
},
'render:errorMiddleware': app => app.use(async (error, req, res, next) => {
logger.page.error({ type: 'render', error, ...req.logParams }, req)
next(error)
})
} // http.js
class Resource {
async http(opts) {
let data
try {
data = await axios(opts)
process.server && logger.api.info(opts, this.req.logParams) // api log with requestId
} catch (error) {
process.server && logger.api.error(opts, error, this.req.logParams) // api error log
}
return data
}
}2.9 Log Collection
Filebeat ships logs to a Kafka cluster, which are then indexed in Elasticsearch and visualized with Kibana, enabling queries such as “all render logs in a time window” or “traffic that reached the Nuxt service after CDN and WAF filtering”.
2.10 Traffic Monitoring
By analysing the enriched logs, the team can compute the real traffic that hits the Nuxt service, distinguishing CDN cache hits, WAF blocks, and Nginx proxy cache hits.
Conclusion
Switching to Nuxt resolved the earlier Velocity‑based problems while introducing new challenges (domain conflicts, shared server variables, rendering performance). Overall, development experience improved dramatically (over 50% faster), component reuse and encapsulation increased, and code readability and maintainability saw a “leap”. With CDN, Nginx, and component caching, rendering performance remained stable; first‑screen load time averages ~0.5 s, error rate ~0.2%, and availability approaches 100% thanks to the disaster‑recovery layer. The team looks forward to Nuxt 3 for further gains.
iQIYI Technical Product Team
The technical product team of iQIYI
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.