Skip to main content
Freelance · 2025Live

FIXPartner

FIXPartner builds software and runs QA for companies in Vietnam. Their website had 135 blog posts that brought in most of their organic traffic. The site ran on WordPress, and Google PageSpeed scored it 17 out of 100 on mobile.

Next.js 15React 19Payload CMS 3MongoDBDockerNginxGoogle Gemini
What was broken

A slow site that was difficult to edit

Lighthouse report
17Performance
58Accessibility
52Best Practices
61SEO
First Contentful Paint6.2s
Largest Contentful Paint11.4s
Total Blocking Time2,340ms
Cumulative Layout Shift0.42

Every page loaded jQuery, three slider plugins, Elementor, and six Google Fonts. The blog index fetched all 135 posts in one query. The site scored 17 on mobile PageSpeed and 47 on desktop.

To change a service description, the CEO had to find a nested object in an Elementor JSON block. Edits often broke the layout, and repairing the page took longer than writing the content. She had stopped updating the site.

The Core Web Vitals checks were all red. Since clients found the company through Google, fixing the slow pages was a priority.

What I built

Running the site with Docker Compose

I put the database, CMS, Next.js server, and Nginx reverse proxy in Docker containers. One Docker Compose command starts them together, and the same setup handles deployment to a new server.

Why Payload over Elementor

The CEO can edit content in Payload CMS and preview the page before publishing. A service-page update takes about five minutes, compared with the afternoons she had spent fixing layouts in Elementor.

Why Gemini in the editor

The company's 135 blog posts are its main source of leads. I added Gemini suggestions to the editor so writers could review SEO issues while working on a draft.

BrowserCDN + SSR pagesNginxSSL · cache · proxyNext.jsSSR + static blogPayloadCMS + live previewGeminiSEO in editorMongoDBContent store
The migration

Migrating all 135 posts

migrate.ts
$ node migrate.ts --source=wordpress --target=payload
→ Connecting to WordPress REST API...
→ Found 135 posts, 24 categories, 89 tags
→ Fetching post content and metadata...
→ Stripping Elementor shortcodes...
→ Converting inline styles to semantic HTML...
→ Uploading featured images to CDN...
→ Creating Payload CMS entries...
✓ 135/135 posts migrated
✓ 24/24 categories mapped
✓ 89/89 tags preserved
✓ 0 errors, 0 content diffs
✓ All URL slugs preserved

I wrote a Node.js script to fetch all 135 posts through the WordPress REST API, including titles, body HTML, featured images, categories, tags, dates, and author data.

The script removed Elementor shortcodes, converted inline styles to semantic HTML, and uploaded every featured image to the new CDN. I preserved the URL slugs so existing links would still work.

I compared the post bodies before and after migration. The checks found no content differences or broken links across all 135 posts.

What shipped

PageSpeed scores from 93 to 100

In the release checks, all 14 pages scored between 93 and 100 on PageSpeed. The CEO can publish posts and update service pages through the CMS. I delivered the site in under three weeks from our first conversation.

1794

Mobile PageSpeed

4799

Desktop PageSpeed

135

posts migrated with content intact

< 3 weeks

first call to production