Funcționalități avansate într-un blog modern: Comentarii, Paginare, Upload și SEO

  • Doru Bulubasa
  • 23 June 2025

După ce ai implementat funcționalitățile de bază — articole, autentificare, afișare listă — e timpul să trecem la lucruri serioase: funcționalități avansate care îmbunătățesc experiența utilizatorului și profesionalizează aplicația.

În acest articol vom acoperi:

  • Comentarii și relații între entități (Post ↔ Comments)

  • Paginare, căutare și filtrare articole

  • Upload imagine cover pentru articol (multipart/form-data)

  • SEO: SSR vs SPA – decizii pentru un blog modern


💬 1. Comentarii și relații între entități (Post ↔ Comments)

✅ Modelare în backend

public class Post
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public ICollection<Comment> Comments { get; set; }
}

public class Comment
{
    public Guid Id { get; set; }
    public string AuthorName { get; set; }
    public string Text { get; set; }
    public DateTime CreatedAt { get; set; }

    public Guid PostId { get; set; }
    public Post Post { get; set; }
}

  • Comentariile sunt legate prin PostId

  • Folosește Include(p => p.Comments) pentru a încărca comentariile

✅ Endpointuri

[HttpGet("{id}")]
public async Task<IActionResult> GetPostWithComments(Guid id)
{
    var post = await _context.Posts
        .Include(p => p.Comments)
        .FirstOrDefaultAsync(p => p.Id == id);

    return Ok(post);
}

[HttpPost("{postId}/comments")]
public async Task<IActionResult> AddComment(Guid postId, [FromBody] CommentDto dto)
{
    var comment = new Comment { Text = dto.Text, PostId = postId, CreatedAt = DateTime.UtcNow };
    _context.Comments.Add(comment);
    await _context.SaveChangesAsync();
    return Ok();
}

✅ React

const PostPage = () => {
  const [post, setPost] = useState<Post | null>(null);

  useEffect(() => {
    fetch(`/api/posts/${id}`).then(res => res.json()).then(setPost);
  }, []);

  return (
    <>
      <h1>{post?.title}</h1>
      <p>{post?.content}</p>
      <h3>Comentarii</h3>
      <ul>
        {post?.comments.map(c => (
          <li key={c.id}><strong>{c.authorName}</strong>: {c.text}</li>
        ))}
      </ul>
    </>
  );
};


🔍 2. Paginare, căutare și filtrare articole

✅ Backend

[HttpGet]
public async Task<IActionResult> GetPaged([FromQuery] int page = 1, [FromQuery] string? search = null)
{
    var query = _context.Posts.AsQueryable();

    if (!string.IsNullOrEmpty(search))
        query = query.Where(p => p.Title.Contains(search));

    var pageSize = 10;
    var items = await query
        .OrderByDescending(p => p.CreatedAt)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();

    return Ok(items);
}

✅ React – componente

const [search, setSearch] = useState('');
const [page, setPage] = useState(1);

useEffect(() => {
  fetch(`/api/posts?page=${page}&search=${search}`)
    .then(res => res.json())
    .then(setPosts);
}, [page, search]);

✅ Poți adăuga componente ca:

  • <Pagination /> (cu butoane + număr pagină)

  • <input type="search" /> pentru titlu/conținut


🖼️ 3. Upload imagine cover pentru articol

✅ Backend (multipart/form-data)

[HttpPost]
public async Task<IActionResult> Upload([FromForm] CreatePostWithImageDto dto)
{
    var file = dto.Image;
    var fileName = Guid.NewGuid() + Path.GetExtension(file.FileName);
    var path = Path.Combine("uploads", fileName);

    using var stream = new FileStream(path, FileMode.Create);
    await file.CopyToAsync(stream);

    var post = new Post { Title = dto.Title, Content = dto.Content, ImageUrl = $"/uploads/{fileName}" };
    _context.Posts.Add(post);
    await _context.SaveChangesAsync();
    return Ok();
}

✅ React + FormData

const handleSubmit = async (e) => {
  e.preventDefault();
  const data = new FormData();
  data.append("title", title);
  data.append("content", content);
  data.append("image", file);

  await fetch("/api/posts", {
    method: "POST",
    body: data
  });
};


🌐 4. SEO și rendering: SSR vs SPA

SPA (Single Page Application)

Avantaje:

  • Încărcare rapidă după primul request

  • Ușor de dezvoltat

  • Bun pentru dashboard-uri, aplicații interne

Dezavantaje:

  • Nu e ideal pentru crawlere (Googlebot etc.)

  • Fără prerendering, titlurile și meta tag-urile nu sunt vizibile

SSR (Server-Side Rendering) – opțional în viitor

Poți migra la:

  • Next.js (React SSR/SSG)

  • Prerender.io + caching proxy

  • Sau poți genera meta tag-uri dinamice în backend pentru anumite URL-uri cu aspnetcore + blazor-server.


✅ Concluzie

Aceste funcționalități transformă un blog simplu într-o aplicație completă și scalabilă:

✅ Comentarii și entități conectate

✅ Paginare și filtre pentru UX optim

✅ Upload imagini cover în mod elegant

✅ Gândire SEO pentru vizibilitate pe web

Scrie un comentariu

Adresa de mail nu va fi publicata. Campurile obligatorii sunt marcate cu *