After publishing the new version of my portfolio, I wanted to review something more specific than the design or the stack: whether the site was prepared to be crawled, understood and indexed correctly.
I did not want to stop at adding a title, a description and a sitemap. My goal was for every page to be published with a coherent SEO base from the beginning: clear URLs, consistent metadata, structured data, language alternates, good performance and settings that make crawling easier.
This is the technical work I did.
Designing the architecture around specific searches
The first SEO decision is not in a meta tag, but in the architecture. Each page needs to answer a specific intent and have a stable URL that can be linked, indexed and measured.
In my portfolio, that structure is split into:
- Home.
- About.
- Projects.
- Individual page for each project.
- Blog.
- Individual page for each article.
- Contact.
- Spanish and English versions.
This allows every URL to have a clearer intent. A project page can rank for the project name, the technology used or the type of work done. An article can rank for a specific question. The home page does not have to solve every possible search.
Astro fits this part well because it generates static HTML per page. I do not need Google to wait for a client-side app to render the main content. The HTML already arrives with structure, headings, text and links.
That advantage does not make Astro the best option for every project. In my Astro vs WordPress comparison, I separate technical control from the real editing and maintenance needs of a professional website.
Centralising metadata
One of the first pieces I built was a reusable SEO component. Instead of repeating <title>, description, canonical, Open Graph, Twitter Cards and robots tags on every page, everything goes through one layer.
This gives me three advantages:
- I avoid missing metadata between pages.
- I keep the format consistent.
- I can change a global rule without editing half the project.
Each page provides its title and description, while the component completes the rest: canonical, social image, theme-color, meta robots, Open Graph and Twitter.
For a small site it may look like too much. In practice, once you add projects, a blog and languages, centralising it prevents very common mistakes.
Consistent canonicals and trailing slash
One detail that looks small, but is not: URLs must be consistent.
In my case, I use trailing slash consistently. That means the canonical URL of a page always ends in /. The sitemap also generates URLs using that same format.
The goal is to avoid duplicates such as:
/projects/projects//projects/index.html
If Google finds several ways to access the same content, it can usually understand it, but I prefer not to make it work harder. One URL, one canonical, one clear version.
Hreflang for Spanish and English
The portfolio has Spanish and English versions, so each page emits alternates:
esenx-default
The sitemap also includes those alternates. This matters because translating pages is not enough. Search engines need to know which version belongs to each language and which one is the default.
In my case, x-default points to the Spanish version because it is the main language of the site.
Structured data with JSON-LD
I added JSON-LD so search engines can better understand what each page represents.
The site includes schemas such as:
Person, to identify me as the author and professional.WebSite, to describe the site.ProfilePage, on the home and about pages.CollectionPage, on listings such as projects and blog.CreativeWork,SoftwareSourceCodeorWebApplication, depending on the project type.BlogPosting, on each article.BreadcrumbList, to reinforce the navigation hierarchy.
I do not expect this to rank by itself. Structured data does not replace content. But it reduces ambiguity: it helps connect the author, site, projects and articles as part of the same entity.
Dynamic sitemap
I do not maintain the sitemap manually. It is generated from the real content in the project.
It includes:
- Static pages.
- Projects.
- Blog articles.
- Last modification date.
- Change frequency.
- Priority.
- Language alternates.
This prevents the sitemap from becoming outdated when I publish a new project or article. I also added a sitemap.xsl stylesheet so that, if someone opens it in the browser, they do not just see plain XML but a readable table.
The sitemap is not magic, but it makes crawling easier. And on a site that grows with content, keeping it automated is a healthy decision.
Robots.txt without blocking important resources
The robots.txt allows the site to be crawled and points to the sitemap. It also explicitly allows AI bots and search engines to access the content.
One important point: I do not block /_astro/. That is where Astro-generated assets live, such as CSS and JavaScript. If you block those resources, Google may have more trouble rendering the page like a real user.
I do block /api/, because it is not an area I want crawled if I add endpoints in the future.
llms.txt and llms-full.txt
Beyond classic SEO, I added llms.txt and llms-full.txt.
The idea is simple: provide a structured version of the content for AI assistants. llms.txt works as a short index of the site and llms-full.txt gathers more context in Markdown format.
It is not a standard equivalent to robots.txt, but I find it useful for a personal site. If someone searches for information about me or my projects through an assistant, I want the content to be easy to find, cite and summarise correctly.
I keep them accessible, but configured as support files rather than main pages of the site.
Performance as part of SEO
Technical SEO is not just tags. How the site loads also matters.
That is why I made several decisions:
- Use Astro to generate static HTML.
- Avoid turning everything into a SPA.
- Reduce client-side JavaScript.
- Serve the Inter font locally.
- Organise Sass with a maintainable architecture.
- Cache Astro-generated assets for a long time.
- Avoid heavy animations that did not support the content.
My previous portfolio had more visual effects. This one is more restrained, but it loads better and is easier to maintain. For a professional site, I prefer the content to be fast, clear and crawlable.
Hosting configuration
I also reviewed the hosting configuration so each type of resource is served correctly.
In practice, this means paying attention to three things:
- Technical files should use the correct content type.
- Static resources should be cacheable without making important updates harder.
- Global configuration should not block files needed to render or crawl the site.
It is not the most visible part of SEO, but it can matter a lot. A sitemap served incorrectly, a blocked resource or overly aggressive caching can create issues that are hard to spot if you only look at the HTML.
Content before tricks
The technical side helps, but it does not replace content. For the portfolio to have a better chance of ranking, I needed pages with real text, not just nice cards.
Each project has its own context:
- What problem it solved.
- What role I had.
- What stack I used.
- What I learned.
- What I would improve.
That turns each project into a useful page, not just a screenshot with a link. The same applies to the blog: writing about real decisions in the project lets me create content connected to my experience.
What I learned
The main conclusion is that technical SEO works better when it is part of the architecture from the beginning.
It is not about adding a plugin, generating a sitemap and forgetting about it. It is about making coherent decisions:
- Clear URLs.
- Rendered HTML.
- Consistent metadata.
- Structured content.
- Good performance.
- Resources accessible to bots.
- Structured data.
- Sitemap and robots aligned.
- Hosting settings that do not block crawling.
My goal was not to chase shortcuts, but to build a solid base. If I publish more projects and articles, the system is already prepared so every new page starts with a correct SEO structure from day one.