· 8 min read

Setting Up a Server at the Company Wasn't as Simple as I Thought

This article was auto-translated from Chinese. Some nuances may be lost in translation.

Motivation

During this project development, the planning team requested calling APIs to fetch data for dynamic updates on our previously purely static landing pages. Coupled with increasingly complex user interactions, our legacy static stack of pug + webpack + jQuery was no longer cutting it. Therefore, we introduced next.js in the new version.

Let’s start with the original landing page architecture. During CI runs, static HTML pages were bundled, and CSS, JavaScript, and images were uploaded to a CDN. The HTML files were placed directly on a server, with an Nginx reverse proxy sitting in front of it.

Huh? If everything else is pushed to a CDN, why keep the HTML on a server?

That comes down to historical reasons. First, the company uses an in-house built CDN with limited custom domain support. If HTML files were also uploaded to the CDN, the domain name would change, which was undesirable for the planning team.

Second, in addition to landing pages built by frontend engineers, campaign landing pages are sometimes generated using a landing page template generator created for marketing/planners, and its domain is fixed and cannot be changed. To accommodate both (templated landing pages and engineer-built landing pages), using Nginx as a reverse proxy was a practical compromise.

Here’s the catch: Next.js was incompatible with the existing architecture. Rewriting all landing pages in React seemed too unrealistic, so we decided to add a new server dedicated to landing pages built with Next.js, and use Nginx routing to ensure they shared the same domain.

The Problems

Before diving deeper, let’s look at the problems with the original static server and how introducing a dedicated server would improve things.

1. SEO

The primary concern was SEO. While the original static server could handle basic SEO, as mentioned earlier, requirements like calling APIs to fetch data or publishing blog posts presented a challenge. Since pages were generated at build time, fetching fresh API data meant relying on client-side JavaScript via Ajax or fetch, which cannot be effectively indexed for SEO. Another factor was CORS: because the API server and the landing page lived on different domains, CORS issues were inevitable.

2. Performance

Although the amount of data used on a landing page isn’t huge, leveraging SSR (Server-Side Rendering) still brings noticeable performance gains.

3. Benefits of Having a Server

Having Node.js as a backend server provides much more flexibility to accommodate future planning requirements. Next.js natively supports multiple build and rendering strategies—for instance, using getStaticProps to pre-render static files, and getServerSideProps for SSR. A Node.js backend also makes it straightforward to implement caching or database access when needed.

For these reasons, we decided to spin up a new server. Little did I know, this was only the beginning of our troubles.

The Struggle

Coordinating with SRE

To set up a server in our project, we first had to coordinate with the SRE team and explain our use case. Perhaps due to the cautious nature typical of SREs, they had many concerns and were also busy managing other projects, so communication took quite a bit of time. However, our rationale was clear, and because I had clarified the entire architecture beforehand, we received approval fairly quickly.

We run on a private cloud. In the alpha environment, every developer can provision their own machines, so setting up alpha went smoothly. However, the beta and production environments required much more complex procedures: requesting ACLs and having SRE handle provisioning. While spinning up machines is simple, installing various packages is another story—monitoring agents, Node.js, Nginx, and more. Thankfully, SRE helped out so the installations went fine.

A Legacy-Laden Nginx Config

Provisioning the server was easy; what really gave me a headache was the ancient Nginx configuration file. It was packed with redirects from various sources, maintenance mode handling, and special rule exceptions for specific paths.

Deploying via Ansible Playbooks

While SRE helped set up the environment, deployment was still on us. I had to figure this part out myself. The company mainly uses Ansible playbooks + awx for deployment (AWX is a web-based GUI and API for running playbooks). Fortunately, having collaborated with colleagues on other projects, I had some playbook experience; otherwise, that massive pile of YAML would have been completely dizzying.

Docker

For convenience, recent deployment practices involved provisioning a machine with Docker installed and creating a systemctl service inside to run the Docker image.

I thought building the image would be a breeze, but implementation revealed problem after problem. This again traces back to project legacy: the repo used a Lerna monorepo structure where each subfolder referenced root-level files, making it tricky to bundle just a single subfolder.

Lerna has two main characteristics:

  • Package installation hoists dependencies used across projects to the root node_modules.
  • Sub-projects can import functions from sibling sub-projects via import a from 'sub-project' (implemented under the hood via symbolic links in node_modules).

Bundling everything resulted in an image size exceeding 1 GB, reminding me once again just how terrifying node_modules can be. The symbolic links also caused hours of debugging: without properly creating symlinks, npm would attempt to look up sub-project from the public registry on the internet—which naturally couldn’t be found.

Jenkins Integration

The company uses Jenkins for CI/CD, so Docker images are built and pushed to our internal Docker Hub via Jenkins. I later noticed that Jenkins occasionally threw unexplained errors during builds, only to work fine again after a few retries.

Risk Assessment

Because we introduced a new server, company policy required going through a formal Risk Assessment.

Security Check

Even though major projects always involve security checks, spinning up a new server also required the Security Team to review security vulnerabilities and mandate necessary fixes.

Misjudging the Timeline

The adoption of Next.js wasn’t initiated by our team; colleagues in another office had introduced it in other projects. I initially assumed the server infrastructure was already in place and that we could just reuse it for our landing pages. In reality, they had only used Next.js’s SSG (Static Site Generation) feature, simply dropping the exported static files onto the existing static landing page server.

While caught off guard, QA was still more than a month away, and I thought hooking up Next.js’s server features would leave plenty of time. However, QA testing for some features had already begun, and development on the landing page itself hadn’t even started. Although building the page took only a week, handling QA, communicating across departments, designing the architecture, and going through all the aforementioned processes ate up an entire month before I knew it. On top of that, a new project entered its estimation phase, release deadlines loomed, and I was overwhelmed with no one around to help.

During this period, the stress was immense, to the point where I was working overtime on weekends and close to burning out.

Reflections

Unexpected requirements like this usually demand far more buffer time for preparation. Honestly, goals like “SEO improvements” aren’t particularly glamorous… and rarely translate into notable performance reviews. Ah, as I get older, I really need to rethink my approach to work.

1. I Became the Bottleneck

Setting up the server this time involved too much non-frontend knowledge and required deep familiarity with internal tooling. Much of it was figured out step-by-step through reading docs. Since it had little to do with frontend dev, it was hard to get teammates to help. I wondered if there was a better way, but that’s just how these things go: if you give up and walk away, the proposal gets shelved, and nobody wants to step into the mess. Still, this definitely isn’t a healthy pattern.

2. Underestimating the Complexity of Server Setup

Rather than saying server setup itself is difficult, it’s the cross-department communication and subsequent preparation steps that inevitably stretch out the timeline. Frankly, you never truly know until you go through it once. What I can do next time is document this entire experience so future efforts are much smoother.

3. Most Departments Are Predominantly Japanese

Apart from our team, most other departments are predominantly Japanese. When Japanese proficiency is lacking, communication easily breaks down, so I often ended up acting as a bridge or having to participate in everything myself. That isn’t ideal either. I’m still figuring out how to handle this better.

4. Insufficient Upfront Preparation

Not realizing earlier that there was no server set up was due to my own insufficient upfront investigation, which I need to reflect on. If similar server setup requirements arise in the future, it would be much wiser to allocate a longer timeline to guard against unforeseen roadblocks.

5. Conflicting Timelines and Priorities

Beyond server provisioning, multiple deadlines collided at the same time: fixing QA bugs, building the landing pages, code reviews, SRE discussions, and debating scope changes. The constant context switching heavily compressed the focus time needed for setting up the server.

Related Posts

Explore Other Topics